Java 提供了一種對象序列化的機制,該機制中,一個對象可以被表示為一個字節序列,該字節序列包括該對象的數據、有關對象的類型的信息和存儲在對象中數據的類型。
Java反序列化
反序列化就是將字節序列恢復為Java對象的過程
整個過程都是 Java 虛擬機(JVM)獨立的,也就是說,在一個平臺上序列化的對象可以在另一個完全不同的平臺上反序列化該對象,因此可以實現多平臺之間的通信、對象持久化存儲,主要有如下幾個應用場景。
HTTP:多平臺之間的通信,管理等
RMI:是 Java 的一組擁護開發分布式應用程序的 API,實現了不同操作系統之間程序的方法調用。值得注意的是,RMI 的傳輸 100% 基于反序列化,Java RMI 的默認端口是1099端口。
JMX:JMX 是一套標準的代理和服務,用戶可以在任何 Java 應用程序中使用這些代理和服務實現管理,中間件軟件 WebLogic 的管理頁面就是基于 JMX 開發的,而 JBoss 則整個系統都基于 JMX 構架。
系列化反序列化基礎
序列化和反序列化本身并不存在問題。但當輸入的反序列化的數據可被用戶控制,那么攻擊者即可通過構造惡意輸入,讓反序列化產生非預期的對象,在此過程中執行構造的任意代碼。
一個類的對象能夠序列化的成功需要兩個條件
- 該類必須實現 java.io.Serializable 接口
- 該類的所有屬性必須是可序列化的。如果有一個屬性不是可序列化的,則該屬性必須注明是短暫的。
漏洞基本原理
簡單的反序列化Demo
首先定義對象類Persion,包含兩個參數
1
2
3
4
5
6
7
|
public class implements java.io.Serializable{ public String name; public int age; public void info(){ System.out.println( "Name:" + this .name+ ";nAge:" + this .age); } } |
在主類中聲明對象,并且將對象序列化為二進制文件,將其存儲到硬盤中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import java.io.*; public class Main{ public static void main(String [] args){ 將對象序列化為二進制文件 Persion p = new Persion(); p.name = "Joner" ; p.age = 18 ; try { //打開一個文件輸入流 FileOutputStream fileOut = new FileOutputStream( "D:\test\test.db" ); //建立對象輸入流 ObjectOutputStream out = new ObjectOutputStream(fileOut); //輸出反序列化對象 out.writeObject(p); out.close(); fileOut.close(); System.out.printf( "保存成功" ); } catch (IOException i){ i.printStackTrace(); } } |
進行反序列化
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
|
import java.io.*; public class Main{ public static void main(String [] args){ /*從二進制文件中提取對象*/ Persion persion = null ; try { FileInputStream fileInputStream = new FileInputStream( "D:\test\test.db" ); //建立對象輸入流 ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); persion = (Persion) inputStream.readObject(); inputStream.close(); fileInputStream.close(); } catch (ClassNotFoundException c){ System.out.println( "對象未找到" ); c.printStackTrace(); return ; } catch (FileNotFoundException e) { e.printStackTrace(); return ; } catch (IOException e) { e.printStackTrace(); return ; } System.out.println( "反序列化對象......." ); System.out.println( "Name:" +persion.name); System.out.println( "Age:" +persion.age); } } |
查看test.db文件的內容可以看見如下內容
其中 AC ED 00 05 是java 序列化內容的特征,其中00 05 是版本信息,base64編碼后為ro0AB
反序列化漏洞Demo
在上面的Demo中可以看到,進行反序列化時會調用readObject()方法,如果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
|
import java.io.*; public class Main{ public static void main(String [] args) throws Exception{ Unsafeclass unsafeclass = new Unsafeclass(); unsafeclass.name = "hhhhh" ; FileOutputStream fileOutputStream = new FileOutputStream( "object" ); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); //將對象寫入object文件 objectOutputStream.writeObject(unsafeclass); objectOutputStream.close(); //從文件中反序列化對象 FileInputStream fileInputStream = new FileInputStream( "object" ); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); //恢復對象 Unsafeclass objectFormDisk = (Unsafeclass)objectInputStream.readObject(); System.out.println(objectFormDisk.name); objectOutputStream.close(); } } class Unsafeclass implements Serializable{ public String name; //重寫readObject()方法 private void readObject(java.io.ObjectInputStream inputStream ) throws IOException , ClassNotFoundException{ //執行默認的readObdect()方法 inputStream.defaultReadObject(); //執行打開計算器命令 Runtime.getRuntime().exec( "calc.exe" ); } } |
程序運行過程為:
- UnsafeClass類背序列化進入object文件
- 從object文件中恢復對象
- 調用被恢復對象的readObject()方法
- 命令被執行
這樣看感覺并不會有人會這樣寫readobject()這個方法,而且一些成熟的框架都會有防范反序列化的方法,但仍有很大比例的反序列化漏洞,這主要是使用了不安全的庫造成的。上面只是介紹了簡單的Java反序列化過程,接下來會有一篇文章介紹反序列化漏洞檢測方法以及復現一些經典反序列化漏洞。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.dazhuanlan.com/2020/06/12/5ee3439045cce/