企业博客
此文旨在简单记录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());
第二步:从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如下:
最后创建一个方法,模拟内存马注入:
看下效果: