Java反序列化漏洞

1.Java序列化与反序列化

序列化与反序列化对于 Java 程序员来说,应该不算陌生了,序列化与反序列化简单来说就是 Java 对象与数据之间的相互转化。

  • **Java序列化:**把Java对象转换为字节序列的过程,便于保存在内存、文件、数据库中。
  • **Java反序列化:**把字节序列恢复为Java对象的过程,ObjectInputStream类的readObject()方法用于反序列化。


那为什么要序列化文件呢?
实质上,序列化机制并不只局限于 Java 语言,序列化的本质是内存对象到数据流的一种转换,我们知道内存中的东西不具备持久性,但有些场景却需要将对象持久化保存或传输。例如缓存系统中存储了用户的 Session,如果缓存系统直接下线,带系统重启后用户就需要重新登陆,为了使缓存系统内存中的 Session 对象一直有效,就需要有一种机制将对象从内存中保存入磁盘,并且待系统重启后还能将 Session 对象恢复到内存中,这个过程就是对象序列化与反序列化的过程,从而避免了用户会话的有效性受系统故障的影响。
此外,在 Java 工程中,序列化还广泛应用于 JMX,RMI,网络传输(协议包对象)等场景,可以说序列化机制赋予了内存对象持久化的机会,就像虚拟机镜像(VMware Take a snapshot),也可以将序列化机制看作是内存对象的一种镜像机制。
(这里要是不懂的话可以多读几遍)

我们来简单实现以下Java序列化与反序列化之间的过程:
解释:
我们先将一句话进行序列化,然后保存为test.txt文件,然后对其进行反序列化,将其还原出来!
image.png
代码附上:

package Demo; 
import java.io.*;

public class Main {
    public static void main(String[] args) {
        Main m = new Main();
        try {
            m.run();
            m.run2();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public void run() throws IOException {
        FileOutputStream out = new FileOutputStream("test.txt");
        ObjectOutputStream obj_out = new ObjectOutputStream(out);
        User u = new User();
        u.setName("Java是世界上最好的语言");
        obj_out.writeObject(u);
        obj_out.close();
        System.out.println("User对象序列化成功!");
    }

    public void run2() throws IOException, ClassNotFoundException {
        FileInputStream in = new FileInputStream("test.txt");
        ObjectInputStream ins = new ObjectInputStream(in);
        User u = (User) ins.readObject();
        System.out.println("User对象反序列化成功!");
        System.out.println(u.getName());
        ins.close();
    }
}
package Demo; 

import java.io.Serializable;

public class User implements Serializable {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

2.Externalizable接口

实现Externalizable接口进行序列化和反序列化,必须实现writeExternal、readExternal方法,并且还要实现一个类的无参构造方法。

 import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
 
public class biu implements Externalizable {
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    private String name;
    //实现无参构造方法
    public biu(){
        System.out.println(this.getClass()+"无参构造方法被调用");
    }
    //实现writeExternal方法
    public void writeExternal(ObjectOutput out) throws IOException {}
    //实现readExternal方法
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {}
}

用法和Serializable接口一样!

3.readObject()方法

这个方法在反序列化漏洞中起到了重要的作用,因为在序列化过程中,JVM虚拟机会试图调用对象类里的 writeObject()和 readObject() 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject() 方法以及 ObjectInputStream 的 defaultReadObject() 方法。
实现了Serializable接口可以执行的方法包括readObject()、readObjectNoData()、readResolve(),以及实现了Externalizable接口的readExternal()方法。这些在找反序列化漏洞时都需要重点关注。

import java.io.ObjectInputStream;
import java.io.Serializable;
public class user implements Serializable{
    public user(){
        System.out.println(this.getClass()+"无参构造方法被调用");
    }
    public user(String name){
        System.out.println(this.getClass()+"user(String name)构造方法被调用");
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString(){
        System.out.println(this.getClass() + "的toString()被调用");
        return "userclass{name="+getName()+"}";
    }
    private void readObject(ObjectInputStream in) throws Exception{
        //执行默认的readObject()方法
        in.defaultReadObject();
        System.out.println(this.getClass() + "的readObject()被调用");
        //windows重点
        Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
    }
    private String name;
}

在readObject()方法中存在Runtime.getRuntime().exec(new String[]{“cmd”, “/c”, name});name为执行命令参数,那么我们可以构造一个恶意的对象,将name赋值为我们想要执行的命令,那么当反序列化时就可以触发readObject()造成RCE。

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileInputStream;
 
 
public class UserSerializable {
    public static void main(String[] args) throws Exception {
        user user = new user("calc");
        user.setName("calc");
        //序列化对象
        serialize(user);
        //反序列化
        user user1 = unserialize();
        System.out.println(user1);
    }
        public static void serialize(user user) throws Exception {
            FileOutputStream fout = new FileOutputStream("user.ser");
            ObjectOutputStream out = new ObjectOutputStream(fout);
            out.writeObject(user);
            out.close();
            fout.close();
            System.out.println("序列化完成.");
    }
        public static user unserialize() throws Exception{
 
            FileInputStream fileIn = new FileInputStream("user.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            user user = (user) in.readObject();
            in.close();
            fileIn.close();
            System.out.println("反序列化完成.");
            return user;
        }
}

可以看到在上图中不仅触发了重写的readObject()方法弹出calc程序,而且还触发了如toString()方法、setName()方法等,所以在实际寻找利用链时应该不局限于readObject()方法,其他方法中如果有执行命令的函数也可以进行利用。
image.png
当然,在实际情况中不可能会出现开发这样编写代码,这里是为了方便展示而进行编写,所以反序列化漏洞通常会需要Java的一些特性进行配合比如反射(invoke),然后就是利用链的寻找。
一般,java反序列化漏洞需要三个东西

  • 反序列化入口
  • 目标方法
  • 利用链

补充

标志

数据以rO0AB开头,基本可以确定这串就是JAVA序列化base64加密的数据。
如果以aced开头,那么他就是这一段java序列化的16进制

工具

ysoserial是一个生成序列化payload数据的工具。

java -jar ysoserial-0.0.4-all.jar CommonsCollections1 '想要执行的命令' > payload.out
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小秋LY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值