初識Java反序列化
前言
研究某產品反序列化EXP時,搜集到的POC只有一段16進制字節序列難以利用,遂有下文對Java序列化和反序列化的學習。
大致內容如下:
- 序列化和反序列化示例
- 序列化數據組成解構
- 反序列化漏洞形成原理
序列化和反序列化
序列化:將程序運行時所需要的Java對象轉化為字節序列并存儲在文件系統中,一般為.ser后綴的文件,ObjectOutputStream.writeObject()方法可以將對象序列化。
反序列化:將存儲在文件系統的字節序列轉化成對象供程序使用,ObjectInputStream.readObject()方法可以將字節序列轉化成對象。
可序列化的類必須繼承 java.io.Serializable;序列化機制使對象得以脫離程序之外獨立存在。
示列:
Employ.java
import java.io.Serializable;
//該類必須實現java.io.Serializable
public class Employ implements Serializable {
public String name;
public int age;
}
test.java
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class test {
public static void main(String[] args) {
//將e轉化為字節序列存儲于/tmp/1.ser
Employ e = new Employ();
e.name = "zhangyida";
e.age = 15;
try {
FileOutputStream fops = new FileOutputStream("/tmp/1.ser");
ObjectOutputStream obos = new ObjectOutputStream(fops);
obos.writeObject(e);
obos.close();
System.out.println("Serialized data is saved in /tmp/1.ser");
}catch (IOException i){
i.printStackTrace();
}
}
}
desEmploy.java
import java.io.ObjectInputStream;
public class desEmploy {
public static void main(String[] args) {
Employ e = new Employ();
try {
FileInputStream fis = new FileInputStream("/tmp/1.ser");
ObjectInputStream obis = new ObjectInputStream(fis);
e = (Employ) obis.readObject();
}catch (IOException i){
i.printStackTrace();
return;
} catch (ClassNotFoundException ex) {
System.out.println("Employ class not found!");
ex.printStackTrace();
return;
}
System.out.println(e.name);
System.out.println(e.age);
}
}
運行test.java,生成一個字節序列存儲于/tmp目錄下。

運行desEmploy.java,將存儲在1.ser內的字節序列轉化為對象。

字節序列數據格式

字節序列格式:
一個java對象序列化后的字節序列由三部分組成(magic、version、contents),其中magic和version是常量,magic表示內容類型,version則是版本號,contents是被序列化對象的屬性、狀態等內容。java序列化stream的特征aced 0005及aced 0005編碼后的字符串。
stream:
magic version contents
字節序列中的contents,可能由一個content組成也可以有多個content。
contents content contents content
content由一個或多個的object(對象)、blockdata(數據塊)組成。
content object blockdata
object(對象),序列化的Stream中常見的對象有newObject、newClassDesc、newString。
object newObject newClass newArray newString newEnum newClassDesc prevObject nullReference exception TC_RESET
newObject,表示序列化對象是一個普通object對象;標識符為TC_OBJECT;classDesc表示一個ObjectStreamClass對象,其保存著className、序列化ID、類字段等信息;newHandle是其句柄值,類似對象ID;classdata[],保存類實例化對象屬性。
newObject: TC_OBJECT classDesc newHandle classdata[]
newString,表示序列化對象是一個字符串常量對象。
newString: TC_STRING newHandle
newClassDesc,表示序列化對象是一個ObjectStreamClass對象,TC_CLASSDESC是其標識符;className是其類名;serialVersionID是其序列化版本ID,當對字節序列被反序列化會將此值與本地相應實體類的serialVersionUID比較,一致則反序列化,不一致則拋出異常;classDescINFO保存著類的序列化屬性。
classDescINFO:
classDescFlags(0x02 - SC_SERIALIZABLE or 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE),0x02表示類實現了Serializable接口但并未重寫readObject方法,0x03表示類即實現了Serializable接口又重寫了readObject方法。當值為0x03時,反序列化該字節序列時會調用重寫的readObject方法。
fields:類的屬性
classAnnotation:類注解,一般為TC_ENDBLOCKDATA
superClassDesc:被序列化對象的類的父類是否可序列化,可序列化時則寫入父類的classDesc,不可序列化時則為TC_NULL。
newClassDesc
TC_CLASSDESC className serialVersionUID newHandle classDescInfo
*classDescInfo
classDescFlags fields classAnnotation superClassDesc
使用SerializationDumper可以查看其序列化之后的相關信息:Employ類的實例對象,Int屬性age值為15,String屬性name值為zhangyida;Employ類未重寫readObject方法。

用途及使用場景
用途:
1、將對象的字節序列永久保存在硬盤以便使用。
2、網絡傳輸。
使用場景:
使用場景豐富,簡單列舉常用場景。
1、服務器啟動后,將用戶session信息永久保存在硬盤中,當服務器出現問題需要重啟時可以直接從硬盤中讀取字節序列還原用戶seesion。
2、JNDI、RMI遠程代碼調用
3、xml Xstream、XMLDecoder等(HTTP body:Content-Type:applicatin/xml)、json(Jackson、fastjson)http請求中包含。
4、...
反序列化命令執行漏洞原理
被序列化對象的類重寫了readObject方法,且重寫的readObject方法存在問題可執行java代碼造成反序列化命令執行漏洞。
示例:
Employ類重寫readObject方法,添加一個命令執行的方法
Employ.java
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.IOException;
public class Employ implements Serializable {
public String name;
//public int age;
private void test(String name){
System.out.println(name);
}
private void readObject(ObjectInputStream objin) {
try {
objin.readObject();
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
重新生成字節序列后再反序列化字節序列將執行系統命令彈出計算器,故Java反序列化漏洞就是當前類重寫readObject方法時可調用執行系統命令的函數造成遠程命令執行的漏洞。因此真實環境中的java反序列化漏洞需要去尋找重寫了readObject方法的且可利用的類能通過調用Runtime.getRuntime().exec()或者其他函數來達到執行系統命令目的,這個可利用的類稱之為gadget,調用過程稱為gadget chain。
文章來源:Tide安全團隊