从可控点探测、模板引擎识别到上下文判断的 SSTI 解题流程。
🧠 CTF SSTI 做题完整流程图#
① 找“会进模板渲染”的可控点(参数/表单/头/富文本) ↓② 证明是否进了模板(最小回显探测) ↓③ 判断注入类型(SSTI / 仅字符串拼接 / 过滤转义) ↓④ 指纹识别模板引擎(语法差异 + 报错信息) ↓⑤ 确认执行能力边界(能算数?能取变量?能访问对象?) ↓⑥ 绕过过滤(括号/引号/点号/下划线/关键字/空格等) ↓⑦ 选择最短拿 flag 路径(模板变量泄露 / 任意读文件 / 命令执行) ↓⑧ 构造利用链(对象遍历 → 反射/内置函数 → 文件/命令/环境变量) ↓⑨ 稳定回显与定位 flag(路径枚举/源码读取/环境变量/容器特征) ↓⑩ 收尾(整理通用 payload、总结过滤点、复现写出)① 找“会进模板渲染”的可控点#
重点不是“能不能输入”,而是你的输入是否被当成模板渲染。高频点:
- 邮件/消息模板预览、工单回复预览、评论展示
- “欢迎语/签名/昵称”这类会被拼进 HTML 的字段
- 报错页、404 页面、站内搜索结果页
- 导出(PDF/HTML)、打印预览、分享链接页
**小技巧:**不仅看参数,Header 也常进模板:User-Agent / Referer / X-Forwarded-For(CTF里很爱出)。
② 证明是否进了模板(最小回显探测)#
先做**“无害算术”**验证:看它到底是“原样输出”还是“被计算”。
常见探测组合(一次多试几种语法):
{{7*7}}${7*7}#{7*7}<%= 7*7 %>
判断:
- 输出仍是原样:可能没进模板 / 被转义 / 仅字符串拼接
- 输出变成
49:高度疑似 SSTI - 直接 500/报错:也很有价值(能拿引擎线索)
③ 判断注入类型(SSTI / 仅拼接 / 过滤转义)#
你要区分三种情况:
- 真 SSTI:表达式会被执行
- 假模板:只是把
{{ }}当普通文本 - 被转义:比如页面显示
{{7*7}}(HTML 实体化)
这一步决定你是继续“执行链”,还是先想“绕过转义/进入渲染路径”。
④ 指纹识别模板引擎(语法差异 + 报错)#
用语法差异和报错信息最快:
{{7*'7'}}(有的引擎会报类型错误,有的会拼接){{7/0}}(除零报错信息常带引擎栈)- 试控制结构:
- Jinja/Twig 常见:
{% if 1 %}a{% endif %} - Java系(Freemarker/Velocity)常见:
${...}、#set(...)
- Jinja/Twig 常见:
**CTF经验:**报错页里出现 jinja2 / twig / freemarker / velocity / smarty / handlebars / ejs 这些关键词,基本就定了。
⑤ 确认执行能力边界(从“算数”爬梯子)#
按阶梯测试,不要一上来就“RCE payload”:
- 算数:
{{7*7}} - 字符串:
{{"a"~"b"}}(Twig/Jinja常见) - 变量:
{{config}}/{{self}}/${request} - 对象属性:
{{request.args}}、{{''.__class__}} - 内置函数/过滤器:
|join、|attr、?(不同引擎不同)
你要明确:能访问到哪些对象(request/config/app 等),这决定后面能不能读文件/跑命令。
⑥ 绕过过滤(把过滤当“分类题”做)#
常见拦截点:{ } ( ) . _ ' " | 以及关键字(class / import / eval / os)。
绕过思路(按类别):
- 语法替代:用不同拼接/访问语法(比如过滤了
.就尝试[]或引擎的attr机制) - 拆字拼接:把敏感词拆成
'cl'+'ass'之类(看引擎是否支持) - 编码与实体:URL 编码、HTML 实体、Unicode(看服务端解码顺序)
- 利用已有对象:避免“import”,直接从模板上下文里找可用对象链
⑦ 选择最短拿 flag 路径(别执着 RCE)#
CTF里 SSTI 最快拿 flag 常见三条路(从易到难):
- 模板上下文直接泄露:
config / settings / SECRET_KEY / env - 任意文件读取:直接读
/flag、/proc/self/environ、应用源码定位 flag - 命令执行:最后手段(很多题禁网、无回显、或沙箱)
⑧ 构造利用链(“对象遍历 → 能力提升”)#
通用思路是:
- 先找到可用对象(request/app/config 等)
- 再找能到达文件/进程/反射/执行器的路径(每个引擎不同)
- 最后做读 flag 或执行 cat
做题时建议你把 payload 组织成三段:
- 定位对象(我能拿到什么)
- 提升能力(我能调用什么)
- 读取/输出(我怎么把结果回显出来)
⑨ 稳定回显与定位 flag(务实路线)#
优先尝试这些“高命中”位置(根据题目环境灵活换):
/flag、/flag.txt、/app/flag、/home/*/flag- 源码:
/app/app.py、/var/www/html/*、/usr/src/app/* - 环境变量:
/proc/self/environ - 容器信息:
/proc/1/cgroup(判断 docker/k8s 线索)
⑩ 收尾(复现写出)#
写 WP 时按这条主线就很清晰:
- 注入点在哪 → 如何证明 SSTI
- 如何识别引擎
- 过滤点是什么 → 怎么绕过
- 最短利用链是什么 → 怎么拿到 flag
✅ 一句话总纲(和你那份上传题总结同结构)#
1️⃣ 证明能“执行”(确认是 SSTI)2️⃣ 识别“引擎+对象”(确定可达能力边界)3️⃣ 走最短链“读到 flag”(泄露/读文件/执行三选一)