服务器端模板注入(SSTI)完全指南:从原理到实战利用

本文深入解析服务器端模板注入(SSTI)漏洞的形成原理、检测方法和利用技巧,涵盖Jinja2、Twig等主流模板引擎的利用方式,并提供实际代码示例和防御方案,帮助安全研究人员有效识别和防范此类高危漏洞。

服务器端模板注入(SSTI)完全指南

作者:Xcheater
发布时间:2025年7月
来源:InfoSec Write-ups

引言

服务器端模板注入(SSTI)是一个影响重大却最常被忽视的漏洞类型。在进行安全评估时,我们往往会错过这类漏洞。本文将深入探讨该漏洞的基础知识、影响范围以及挖掘方法。

模板引擎基础

模板引擎允许使用占位符和表达式将动态内容注入静态模板。常见的模板引擎包括:

  • Jinja2 (Python)
  • Twig (PHP)
  • Freemarker (Java)
  • Mustache (JavaScript)

模板引擎的核心目的是在静态内容中展示动态内容。当应用程序接收用户名输入并显示个性化内容时,正是通过模板动态生成输出结果。

示例:Had lunch, {{ name }}!

这种方式将用户输入传递给模板并渲染内容。如果未正确处理不受信任的用户输入,将存在安全风险。

什么是服务器端模板注入?

当用户输入未经适当清理或验证直接传递到模板引擎时,恶意用户可以在服务器端模板引擎中注入并执行任意模板代码。

漏洞代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def index():
    user_input = request.args.get('name')  # 从URL获取用户输入
    template = f"Hello, {user_input}!"  # 将用户输入传入模板
    return render_template_string(template)  # 用于动态渲染的函数

if __name__ == '__main__':
    app.run(debug=True)

这是一个Flask + Jinja2的漏洞代码片段,它从URL获取用户输入并直接传递到模板,没有任何清理或验证。

攻击者可以轻易利用此漏洞,通过控制URL中的name参数注入恶意SSTI载荷:
http://xcheater.medium.com/?name={{ 5 * 5 }}
输出结果将为25。这种漏洞可能进一步升级导致远程代码执行(RCE)。

在某些情况下,可能无法立即看到注入载荷的输出,但可能存在盲注或二阶SSTI漏洞。

SSTI的影响

该漏洞可被串联利用,导致:

  • 数据泄露
  • 拒绝服务
  • 跨站脚本攻击
  • 远程代码执行

如何识别SSTI

  1. 识别所有入口点:即用户可控的输入点,重点关注应用程序接收用户数据并处理显示动态内容的位置。

  2. 初始检测:尝试使用以下常见SSTI载荷,检查是否存在错误、数学运算或与常规数据的差异:

    • {{7*7}}, ${7*7}, <%= 7*7 %>, ${{7*7}}, #{7*7}, {{'7'*7}}
    • 如果输出为49,则存在漏洞
  3. 基于错误的检测:触发错误(如)通过错误消息揭示引擎类型

  4. 盲注SSTI:使用时间延迟(如{{ sleep(5) }})或带外(OOB)载荷

利用步骤

步骤1:识别模板引擎

语法指纹识别

  • {{7*'7'}} → Jinja2返回'7777777’(字符串),Twig返回49(数字)
  • ${"a"+"b"} → Mako/Freemarker返回"ab"
  • <%= 7*7 %> → ERB (Ruby)返回49

泄露内置对象

  • {{config}} → Flask配置(Python)
  • {{_self}} → Twig模板范围(PHP)
  • <%= ENV.inspect %> → Ruby环境变量

步骤2:探索可访问内容

根据识别出的引擎类型,探索可访问的内置对象、变量、配置等:

Jinja2 (Python/Flask)

  • {{ config }} → 泄露Flask/Django配置(密钥、调试模式)
  • {{ request }} → 访问Flask请求对象(cookies、headers)
  • {{ url_for.__globals__ }} → 访问全局变量(如ossys

Twig (PHP)

  • {{ _self }} → 模板范围(暴露环境和过滤器)
  • {{ _self.env }} → 环境变量(如数据库凭据)

Freemarker (Java)

  • ${.data_model} → 转储数据模型(传递给模板的变量)
  • ${object?api.class} → 访问Java对象方法(反射)

无法实现RCE的情况

即使发现SSTI,有时也无法获得RCE,原因包括:

  • 模板引擎默认安全配置,阻止访问危险对象
  • 应用程序运行在容器化或受限环境中
  • 使用无逻辑模板引擎(如Mustache、Handlebars)
  • 无服务器设置或权限最小的微服务环境

即使无法实现RCE,仍可利用SSTI读取敏感文件、执行内部SSRF攻击或干扰应用程序业务逻辑。

缓解策略

安全使用模板引擎

  • 使用无逻辑模板引擎(如Mustache、Handlebars)
  • 限制访问可能被滥用的内置对象(如os、sys、subprocess)
  • 阻止危险函数(如eval、globals
  • 避免使用render_template_string()等动态编译函数

输入验证和清理

  • 始终在模板中使用前验证和清理用户输入
  • 转义模板相关字符(如{、}、$、<%)
  • 避免将原始用户输入直接注入模板

最小权限和运行时加固

  • 以最小权限运行应用程序和模板引擎
  • 使用容器或沙箱隔离模板渲染过程
  • 不要给模板引擎访问OS级资源或敏感环境变量的权限

Happy Hacking!
Twitter: https://twitter.com/Xch_eater

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计