云山雾隐 端隐SDP

企业博客

Spring内存马分析

前言

此文旨在简单记录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文件里面加入如下配置:

然后再配置web.xml,加入DispatcherServlet相关配置。

DispatcherServlet 的主要作用是处理传入的web请求,根据配置的URL pattern,将请求分发给正确的Controller 和 View。

然后在resources添加一个springmvc.xml文件,主要配置内容如下:

最后在WEB-INF下随便加个jsp,写个Controller之后就可以直接把项目跑起来:

当访问index时,就返回index,然后前面配置的视图解析器就会到WEB-INF下去找对应的jsp文件。此时如果能看到输出hello world就说明环境搭建成功了。

原理分析

这里主要如有两种思路去分析:

在前面说过,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()); copy

第二步:从ioc容器里面拿到RequestMappingHandlerMapping的实例对象;

RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class); copy

第三步:注册内存马,这里需要构造下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); copy

注入的shell如下:

最后创建一个方法,模拟内存马注入:

看下效果:

References