什么是序列化?
Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。
序列化就是把一个java对象写入到IO流中,以便存入磁盘或者是数据库里,亦或是通过网络进行传输。
如果一个类的数据,需要通过网络进行传输,那么推荐它实现Serializable接口。下面是一个例子。
1 2 3 4 5 6 7 public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(new File("object.txt" )); Date date = new Date(); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(date); }
因为Date本身是实现了接口的,所以可以直接写到文件里,文件内容长这样:
如果用十六进制来查看是这样子的:
其中ac ed 00 05是序列化内容的特征。
对应的反序列化代码也很简单:
1 2 3 4 5 6 7 public static void main (String[] args) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(new File("object.txt" )); ObjectInputStream ois = new ObjectInputStream(fis); Object object = ois.readObject(); object = (Date)object; System.out.println(object); }
当然有几个要注意的点:
反序列创建对象并不会调用构造方法。
一个对象如果想要序列化,那么它的所有成员必须也要支持序列化。
序列化的总体算法是这样的:所有保存到磁盘里的对象都会有一个编号,只有虚拟机检查到这个对象从来没有被序列化过,就会序列化它,否则就只要输出编号即可。
序列化漏洞 示例代码:
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 31 public class SerializableTest implements Serializable { public static void main (String[] args) throws IOException, ClassNotFoundException { FileOutputStream fos = new FileOutputStream(new File("object.txt" )); ObjectOutputStream oos = new ObjectOutputStream(fos); MyObject myObject = new MyObject(); myObject.name = "test" ; oos.writeObject(myObject); oos.close(); FileInputStream fis = new FileInputStream(new File("object.txt" )); ObjectInputStream ois = new ObjectInputStream(fis); MyObject objectFromDisk = (MyObject)ois.readObject(); System.out.println(objectFromDisk.name); ois.close(); } } class MyObject implements Serializable { public String name; private void readObject (java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Runtime.getRuntime().exec("open /Applications/QQ.app/" ); } }
可以看到MyObject实现了readObject方法,该方法的作用正是从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并且加入打开QQ的逻辑。
更加具体的PoC 服务器部分 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 public class ExploitableServer { public static void main (String[] args) { try { ServerSocket serverSocket = new ServerSocket(Integer.parseInt("9999" )); System.out.println("Server started on port " + serverSocket.getLocalPort()); while (true ) { Socket socket = serverSocket.accept(); System.out.println("Connection received from " + socket.getInetAddress()); ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); try { Object object = objectInputStream.readObject(); System.out.println("Read object " + object); } catch (Exception e) { System.out.println("Exception caught while reading object" ); e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } }
服务器部分很简单,就是接收来自客户端的消息,并且将其反序列化即可。
客户端部分 这里因为我更新了java10,导致有些API有些变动。暂时先搁浅吧