Thymeleaf介绍
Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸引人的特点:
Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
Thymeleaf模版注入
Spring ThymeleafView会使用表达式解析模版名称,若是将用户输入的参数拼接到模版路径中,可以造成表达式注入。
以下是漏洞案例代码
1 | "/path") ( |
漏洞复现:
先来看payload: __${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
其中new了一个java.util.Scanner
对象,用于读取字符。传入Scanner中的参数为T(java.lang.Runtime).getRuntime().exec("id").getInputStream()
。
T( )
用于访问类作用域的方法和常量,具体可见这里
java.util.Scanner
是可以省略的,它在此仅用于回显,无需回显时直接T(java.lang.Runtime).getRuntime().exec("id")
执行命令即可。
::
在此为必须,不然不会进入表达式解析过程,并且一定得在表达式后面。
.x
在此处可忽略(return "user/" + lang + "/welcome";
)
解析路径的代码在ThymeleafView:277,其中viewTemplateName
中一定要包含::
,不然不会进入表达式解析过程。
1 | if (!viewTemplateName.contains("::")) { |
跟进parser.parseExpression
方法,在StandardExpressionParser中对~{viewTemplateName}~
进行处理
1 | final String preprocessedInput = |
继续跟进StandardExpressionPreprocessor.preprocess
,在StandardExpressionPreprocessor中,使用正则表达式将路径中的表达式提取出来。其中正则规则:\_\_(.*?)\_\_
(表示非贪婪匹配两个__
之间的内容),匹配得到${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}
提取表达式之后就是根据框架进行对应的表达式解析,这里是Spring所以用的就是SpringEL表达式解析。
另一种情况,方法返回类型为void
(必须为void),此时才会从URI中获取viewname。这种情况比较隐蔽,代码审计时容易忽略。表达式的解析过程是和上面一模一样。
1 | "/doc/{document}") ( |
作者给出的payload: __${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x
和之前的payload相比,少了java.util.Scanner
,直接进行命令执行,无回显。
我在这里试了一下回显的payload,发现报错中没有id
的执行结果
动态调试分析,原因是Spring分配URI时会自动抹去后缀名,导致缺少了.x
,无法正常回显。再加一个.x
就能正常回显。若一个.x
都不加,payload会被截成__${T(java.lang.Runtime).getRuntime()
,导致命令执行失败。
总结
这个漏洞原理很简单,就是Thymeleaf的视图参数可进行表达式解析,若用户输入可控制视图参数,就会导致SpEL注入漏洞产生。
漏洞细节点是payload的构造,需要添加__
和::
引导表达式解析。
参考
https://github.com/veracode-research/spring-view-manipulation
http://itmyhome.com/spring/expressions.html#expressions-types