首先spring+Thymeleaf必过的点
org.springframework.web.servlet.DispatcherServlet#doDispatch
那么先跟入handle()方法
然后跟进handleInternal方法
可以看到mav的获取方法,继续跟进invokeHandlerMethod
继续跟进invokeAndHandle
这里判断returnvalue是否有值,决定了2种类型的payload。returnValue的获取会invoke此Request对应的函数获取返回值,例如下面这个例子访问/path时,获取return的值为user/{lang}/welcome作为ModelAndView的view值。
@GetMapping("/path")
public String path(@RequestParam String lang) {
return "user/" + lang + "/welcome";
}
但如果例子是下面这样的呢?此时ModelAndView的view值为null。
@DeleteMapping(value = "/{username}")
public void deleteUser(@PathVariable(name = "username") String username) {}
}
接着往下来到第二个过程,跟进applyDefaultViewName()。
跟进applyDefaultViewName方法
若ModelAndView对象中的view不为空,则啥也不做,否则会获取DefaultView作为View。来看看defaultview是如何获取的。跟进getDefaultViewName(),(注意:这里我调试没有走到这里,断点走不过去)
继续跟进viewNameTranslator.getViewName()。
跟进getViewName
在该方法中首先获取uri的path值,然后进入transformPath()方法,最后和prefix以及suffix进行拼接,即为ViewName。跟进transformPath()。继续跟进transformPath
在这里会去掉前后的/,它会将.及之后的内容当作扩展名,会截掉.以及之后的内容。因此,这种情况下需要在payload后增加一个.以保证payload内容完整。
解析表达式
获得view之后,接下来跟进processDispatchResult
我们都知道render()是用来模板渲染的,跟进render()
首先获取viewName,然后调用resolveViewName()函数,可以看到此时mv取值为
ModelAndView [view="monitor/cache/cache::${T (java.lang.Runtime).getRuntime().exec("calc.exe")}";
model={cacheNames=[Ljava.lang.String;@7150b74}]
跳入resolveViewName
这里选择bestview,
这里往往返回ThymeleafView,保持了和view一样的值,接着往下
调用了view.render(),即ThymeleafView…render(),跟进
继续跟进
可以看到这里判断viewTemplateName中是否包含::符号,如果包含就StandardExpressionParser.parseExpression来解析,格式为
"~{" + viewTemplateName + "}"
跟进parseExpression
可以看到这是第一次解析,然后return的时候还有第二次解析(Expression)parseExpression(context, input, true);
继续跟进parseExpression
这里首先调用preprocess对__${}__里的内容进行预处理,结果存入 preprocessedInput,然后调用Expression.parse()进行第二次解析表达式。
在第一次处理时,只要__${}__里面的语法正确,是一定会被执行的。执行完的第二次表达式解析是否正确就不一定了。如果第二次执行失败,则会显示原来的viewTemplateName值,而没有回显,但事实上已经执行了恶意语句。影响第二次执行失败与否与~{}里的语法格式有关。
当~{}中出现::,其后面需要有值
最后在如下步骤弹出计算器