前段时间我是写过一篇SpringMvc的分发流程的。
首先我们来复现一下这个漏洞
我这里的环境是Springboot的2.2.0版本。
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
首先这里创建一个控制器: 这里的path参数我们是可控的。
@RequestMapping("/path")
public String path(@RequestParam String path){
return path;
}
Payload:
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream()).next()%7d__::.x
url:
http://localhost:8887/path?path=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22).getInputStream()).next()%7d__::.x
可以看到成功代码执行了。
我们首先定位到doDispatch方法,在dispatchServlet类里面,可以参考我前面写的SpringMvc分发处理。
我们跟进doDispatch方法。首先获取到request之后,然后调用checkMultipart方法,判断是否是上传请求,紧接着调用getHandler方法,根据请求processedRequest获取handler执行链 HandlerExecutionChain,其中包含了适配的handler以及interceptor。跟进去。
来到getHandler方法。首先判断我们的handlerMappings是否等于null,这里的handlerMappings是我们springmvc的9大初始化对象,我们可以发现他是一个List集合,他是在我们的initHandlerMappings方法中进行初始化的。
initHandlerMappings方法,这里传进去一个context就是我们的上下文的context,
然后来到initHandlerMappings方法内部,这里调用GetBean方法 从我们的spring容器中拿到handlerMapping类,这个类型代表我们的controller 这个类型中有我们的controller 以及 我们controller的映射。接着回到我们的getHandler方法。
回到getHandler方法之后, 通过我们上面分析得知handlerMappings中存储着我们的controller以及controller的映射,他是以key=>value的形式进行存储的。
到这里我们的Controller和方法名已经被读取到了 ,然后下面进行循环 此时的mapping就是我们的基于注解的映射 然后拿到了handler 进行返回,我们回到doDispatch方法。
此时的mappedHandler中就存储着Bean工厂 以及对我们方法的映射,
方法的映射里面有我们的方法名 以及我们返回的类型等信息。
我们继续往下走,来到getHandlerAdapter方法,这是一个适配器对象。
通过适配器对象发起调用和请求。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
我们来到适配器对象的方法,这里for循环进行遍历 handlerAdapters。
这里的handlerAdapters是由我们spring 9大初始化对象进行操作的
拿到我们的适配器之后然后进行遍历我们的适配器有几个 这里我们有3个适配器,
一个requestMapping的适配器 就是我们注解开发的适配器。
另外两种是其他方式开发的适配器。
然后进行返回适配器。
继续往下走 这里进行调用我们的拦截器 如果我们有拦截器的话就调用 如果拦截器没有通过的话 则不能往下执行 在调用之前会执行拦截器和调用之后会执行拦截器。
接着我们来到真正调用controller方法进行处理的方法,我们跟进handle方法, 一直f7进入到handleInternal方法。
我们来到handleInternal方法 前面是一些无关紧要的 主要看invokeHandlerMethod处理方法这里传进去三个参数 一个是request请求参数 response响应参数 handlerMethod参数。
这里传进去三个参数 一个是request请求参数 response响应参数 handlerMethod参数,handlerMethod参数是通过 mappedHandler.getHandler()方法进行获取的,其实就是我们上面获取到的类名 方法名 映射的路径,类的类型 参数等等。
我们跟进去。
上面首先进行request对象的包装,接着调用initModel方法 对方法注解参数的处理 比如我们的@RequestBody等等。
紧接着我们调用invokeAndHandle 方法 这里的invocableMethod是被包装过的,里面包装了我们的handlerMethod对象。我们跟进。
来到handlerMethod方法,这里就进行执行请求对应的方法 并获得返回 但是这个方法是void 所以就没有返回值。我们跟进invokeForRequest方法。
来到invokeForRequest方法,首先获取我们请求的参数,可以看到这里有我们的payload。参数拿到之后调用doInvoke方法 我们进去
首先设置访问权限true 因为要反射调用 所以需要设置权限,接着调用controller的方法 返回controller方法的返回值。
跟进去,我们紧接着进入invoke方法 这里是我们jdk的代码 通过反射进行调用我们controller中的方法。
来到invoke方法,继续进入invoke方法,最后通过反射进行调用。
我们回到刚才的invokeAndHandle方法,我们可以看到这里的返回值是我们的payload,这里其实就对应我们的代码:
return path
紧接着会通过Thymeleaf会去查找对应的模版进行渲染。
紧接着调用handleReturnValue方法,首先获取到returnValue的处理器也就是handler,然后调用handleReturnValue对returnValue进行处理。
我们跟进去,首先他判断我们的returnValue是否是CharSequence的实例,然后紧接着调用isRedirectViewName方法判断,我们是否是以redirect:开头,如果是的话设置重定向。然后回到invokeHandlerMethod方法。
然后调用getModelAndView方法,获取到modelandview对象,回到核心类,此时已经获取到了modelandview对象。
紧接着调用processDispatchResult方法进行视图渲染。
我们跟进去,紧接着跟进render方法,将我们的modelandview传进去,也就是mv。
首先首先getViewName方法,获取到我们的视图名称,紧接着调用getView方法获取视图解析器,可以看到这里获取到的视图解析器是thymeleafview,我们跟进render方法。
然后调用renderFragment方法,跟进去。
来到renderFragment方法,这里会判断我们的viewTemplateName是否包含 :: 符号,如果包含的话进入else分支。
在else分支里面,当viewTemplateName中包含::时,thymeleaf会认为其是一个要处理的片段表达式,会给其加上~{}然后进行解析,我们跟进parseExpression尽进行处理。
来到parseExpression方法,继续跟进。首先调用preprocess方法,将
__xxx___
之间的东西提取出来,我们跟进去。
这里会将我们中间的提取出来此时提取出来的就是java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(“open -a Calculato”).getInputStream()).next()}。
然后调用execute执行,跟进execute最终调用org/thymeleaf/standard/expression/VariableExpression#executeVariableExpression使用SpEL执行表达式,触发任意代码执行。
参考:
https://blog.csdn.net/weixin_43263451/article/details/126543803