跳至正文
回到 CTF Notes
CTF WEB NOTES03 / 08

从可控点探测、模板引擎识别到上下文判断的 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:表达式会被执行
  • 假模板:只是把 {{ }} 当普通文本
  • 被转义:比如页面显示 &#123;&#123;7*7&#125;&#125;(HTML 实体化)

这一步决定你是继续“执行链”,还是先想“绕过转义/进入渲染路径”。


④ 指纹识别模板引擎(语法差异 + 报错)#

语法差异报错信息最快:

  • {{7*'7'}}(有的引擎会报类型错误,有的会拼接)
  • {{7/0}}(除零报错信息常带引擎栈)
  • 试控制结构:
    • Jinja/Twig 常见:{% if 1 %}a{% endif %}
    • Java系(Freemarker/Velocity)常见:${...}#set(...)

**CTF经验:**报错页里出现 jinja2 / twig / freemarker / velocity / smarty / handlebars / ejs 这些关键词,基本就定了。


⑤ 确认执行能力边界(从“算数”爬梯子)#

按阶梯测试,不要一上来就“RCE payload”:

  1. 算数{{7*7}}
  2. 字符串{{"a"~"b"}}(Twig/Jinja常见)
  3. 变量{{config}} / {{self}} / ${request}
  4. 对象属性{{request.args}}{{''.__class__}}
  5. 内置函数/过滤器|join|attr?(不同引擎不同)

你要明确:能访问到哪些对象(request/config/app 等),这决定后面能不能读文件/跑命令。


⑥ 绕过过滤(把过滤当“分类题”做)#

常见拦截点:{ } ( ) . _ ' " | 以及关键字(class / import / eval / os)。

绕过思路(按类别):

  • 语法替代:用不同拼接/访问语法(比如过滤了 . 就尝试 [] 或引擎的 attr 机制)
  • 拆字拼接:把敏感词拆成 'cl'+'ass' 之类(看引擎是否支持)
  • 编码与实体:URL 编码、HTML 实体、Unicode(看服务端解码顺序)
  • 利用已有对象:避免“import”,直接从模板上下文里找可用对象链

⑦ 选择最短拿 flag 路径(别执着 RCE)#

CTF里 SSTI 最快拿 flag 常见三条路(从易到难):

  1. 模板上下文直接泄露config / settings / SECRET_KEY / env
  2. 任意文件读取:直接读 /flag/proc/self/environ、应用源码定位 flag
  3. 命令执行:最后手段(很多题禁网、无回显、或沙箱)

⑧ 构造利用链(“对象遍历 → 能力提升”)#

通用思路是:

  • 先找到可用对象(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”(泄露/读文件/执行三选一)