JAVA反序列化(1)
放暑假了,又来学习了。之前讲RMI
反序列化,这篇正式开始JAVA反序列化
的学习
参考文章:《JAVA安全漫谈》
JAVA反序列化安全入门
序列化与反序列化
序列化和反序列化是用来干嘛的
我们需要在网络上去传输数据,可能会用XML、YAML、JSON等数据交互格式。但是它们比较简单,对于比较复杂的数据难以表示清楚。比如想要表示一个对象,如果用JSON或者XML,定义起来就比较费劲,所以像Fastjson
这类序列化库就是在JSON(XML)的基础上进行改造,通过特定语法传递对象,而像这样RMI就是直接通过JAVA内置的序列化方法,将对象转换成二进制进行传输。一旦有涉及到将对象转换成一个指定格式的数据 再从数据中还原出对象的这个过程,就有可能造成一系列安全问题
反序列化方法对比
之前我们也有说过PHP反序列化的东西
Java的反序列化和PHP的反序列化有点类似,都只能将一个对象中的属性按照某种特定的格式生成一段数据流,在反序列化的时候再按照这个格式将属性拿回来,再赋值给新的对象。
JAVA提供了writeObject
来允许开发者在序列化六中插入自定义数据,再在反序列化的时候用readObject
进行读取。PHP则提供了一个魔术方法__wakeup
,在反序列化的时候触发。
1
| Java设计readObject的思路和PHP的__wakeup不同点在于:readObject倾向于解决“反序列化时如何还原一个完整对象”这个问题,而PHP的__wakeup更倾向于解决“反序列化后如何初始化这个对象”的问题。
|
PHP
PHP的反序列化开发者是无法参与的,调用serialize
之后数据就完成了,这就是一个完整的对象。序列化和反序列化都是春内部的内容,只有一些魔术方法在反序列化前后做一些操作,漏洞通常是通过反序列化为入口来控制还原出的对象的属性,从而控制一些危险函数的参数,来进行利用。
P牛这里给出了一个数据库连接的例子
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
| <?php class Connection { protected $link; private $dsn, $username, $password; public function __construct($dsn, $username, $password) { $this->dsn = $dsn; $this->username = $username; $this->password = $password; $this->connect(); } private function connect() { $this->link = new PDO($this->dsn, $this->username, $this->password); } public function __sleep() { return array('dsn', 'username', 'password'); } public function __wakeup() { $this->connect(); } }
|
这里的__wakeup()
就是在反序列化拿到Connection
对象后,执行connect()
函数连接数据库。
__wakeup
的作用是在反序列化之后,执行一些初始化操作。因为资源类型的对象(数据库连接)不会默认写入序列化数据,因此要在反序列化时调用__wakeup
进行连接。
在php中大部分的反序列化漏洞都不是由反序列化造成的,只是通过反序列化可以控制对象的属性,而在代码的执行链路中又有比如eval()、exec()
等危险函数,就可以构造数据来调用到这些危险函数进行危险操作
JAVA
JAVA的序列化操作很多时候需要开发者参与。大量的库都会实现readObject、writeObject
方法。
JAVA在序列化时会调用writeObject
,这个方法接收ObjectOutputStream
类,开发者可以将任何内容写入这个Stream
中,反序列化时,会调用readObject
,开发者也可以从中读取出前面写入的内容,并进行处理
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 32 33 34 35 36 37 38 39 40 41
| package org.example;
import java.io.*; public class Person implements java.io.Serializable { public String name; public int age;
Person(String name, int age) { this.name = name; this.age = age; }
private void writeObject(java.io.ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject("This is a object"); }
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); String message = (String) s.readObject(); System.out.println(message); }
public static void main(String[] args) { Person person = new Person("John Doe", 25);
try { FileOutputStream fileOut = new FileOutputStream("person.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(person); out.close(); fileOut.close(); System.out.println("Person 对象已序列化到 person.ser 文件"); } catch (IOException e) { e.printStackTrace(); } } }
|
执行完main
会生成一个person.ser
在这个项目的根目录下,然后用SerializationDumper
工具来查看此时生成的序列化数据,这里我用的是v1.14
版本的,JDK
版本要稍微高一点我用JDK11
都不行,最后用的JDK17
才运行成功
1
| java -jar .\SerializationDumper-v1.14.jar -r ./person.ser > person.txt
|
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| STREAM_MAGIC - 0xac ed STREAM_VERSION - 0x00 05 Contents TC_OBJECT - 0x73 TC_CLASSDESC - 0x72 className Length - 18 - 0x00 12 Value - org.example.Person - 0x6f72672e6578616d706c652e506572736f6e serialVersionUID - 0x8b 3e 12 c3 8f b4 5e b5 newHandle 0x00 7e 00 00 classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE fieldCount - 2 - 0x00 02 Fields 0: Int - I - 0x49 fieldName Length - 3 - 0x00 03 Value - age - 0x616765 1: Object - L - 0x4c fieldName Length - 4 - 0x00 04 Value - name - 0x6e616d65 className1 TC_STRING - 0x74 newHandle 0x00 7e 00 01 Length - 18 - 0x00 12 Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b classAnnotations TC_ENDBLOCKDATA - 0x78 superClassDesc TC_NULL - 0x70 newHandle 0x00 7e 00 02 classdata org.example.Person values age (int)25 - 0x00 00 00 19 name (object) TC_STRING - 0x74 newHandle 0x00 7e 00 03 Length - 8 - 0x00 08 Value - John Doe - 0x4a6f686e20446f65 objectAnnotation TC_STRING - 0x74 newHandle 0x00 7e 00 04 Length - 16 - 0x00 10 Value - This is a object - 0x546869732069732061206f626a656374 TC_ENDBLOCKDATA - 0x78
|
就可以看到在objectAnnotation
里面有我们调用writeobject
写入的"This is a object"
Python
Python反序列化的过程实际上是在执行一个基于栈的虚拟机。如果可以控制python反序列化的过程就可以去添加一些指令比如一些函数的执行,或是一个完整的应用程序,构造payload完成远程RCE。