群里總有人問反序列漏洞是啥啊,學不明白啊,從來沒聽明白過, 面試有人問,閑聊也有人問;1個月前有人問,1個月后還有人在問
今天就來整理一下個人對反序列化漏洞理解和這個洞涉及的一些知識點
各種語言都有反序列化漏洞
序列化即將對象轉化為字節流,便於保存在文件,內存,數據庫中;反序列化即將字節流轉化為對象
也就是把數據轉化為一種可逆的數據結構,再把這種可逆的數據結構轉化回數據,這就是序列化與反序列化
看到過一個通用的例子(可以應用到很多場景中,比如網絡數據包傳遞接收過程之類的),買一個櫃子,從北京運到上海,由於櫃子形狀怪異,不方便運輸,先把它拆成板子,再裝到箱子里,順豐郵到買家手里,把板子拼裝回櫃子
0x01PHP反序列化漏洞(PHP對象注入漏洞)
PHP通過serialize() 與 unserialize()實現序列化與反序列化
常見的反序列化漏洞中出現的魔術方法及其觸發條件
__construct()當一個對象創建時被調用
__destruct()當一個對象銷毀時被調用
__toString()當一個對象被當作一個字符串時使用
__sleep() 在對象在被序列化之前運行
__wakeup() 如果有,在反序列化之前調用
友情提示:
private屬性序列化的時候格式是 %00類名%00成員名 如testname (test->類名name->成員名)
protected屬性序列化的時候格式是 %00*%00成員名 如*name (name->成員名)
序列化只序列化屬性,不序列化方法,因此
反序列化的時候要保證在當前的作用域環境下有該類存在,類屬性就是唯一的攻擊突破口
(上文提到的__toString()觸發條件非常多)
<?php class test{ public $name = 'lcx'; function __construct(){ echo "__construct()"; echo "<br><br>"; } function __destruct(){ echo "__destruct()"; echo "<br><br>"; } function __wakeup(){ echo "__wakeup()"; echo "<br><br>"; } function __toString(){ return "__toString()"."<br><br>"; } function __sleep(){ echo "__sleep()"; echo "<br><br>"; return array("name"); } } $test1 = new test(); $test2 = serialize($test1); print($test2); $test3 = unserialize($test2); print($test3); ?>
判斷一下魔術方法運行順序
關於PHP序列化輸出結果舉例
$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');
//序列化數組
$s = serialize($a);
echo $s;
?>
運行結果為a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}
數組:3個對象:{1個字符串類型a;5個字符串類型Apple;以此類推}
反序列化與之相反,不舉例了
那么什么情況下會出現反序列化漏洞呢?
借鑒了大佬的一個經典案例來說明,經典永流傳 (文件起名為1.php, PHP中var關鍵字在類內部等同於public)
<?php class Test{ var $test = "123"; function __wakeup(){ $fp = fopen("test.php", 'w'); fwrite($fp, $this -> test); fclose($fp); } } $test1 = $_GET['test']; print_r($test1); echo "<br />"; $seri = unserialize($test1); require "test.php"; ?>
構造payload
O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
1.php?test=O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
傳了Test類有一個test參數 test="<?php phpinfo();?>";
O:4:"Test":1:{s:4:"test";s:18:"<?php%20phpinfo();?>";}
如圖所示
$test1是我可控的沒有任何過濾,我傳了一個Test類,里面有一個test參數等於"<?php phpinfo();?>"
由於代碼中寫了__wakeup()
上文提到,有__wakeup(),在反序列化之前一定會調用此方法,創建了一個test.php文件,並把Test類中的test變量的值即"<?php phpinfo();?>"
寫進了test.php文件,require進行文件包含
(想起來一個事,關於__wakeup(),以前遇到CTF的一個題用到了這個知識點
有當序列化字符串中,表示對象屬性個數的值大於實際屬性個數時,那么就會跳過wakeup方法的執行,即:
O:4:"Test":2:{s:4:"test";s:18:"<?php%20phpinfo();?>";}這種 你就會繞過__wakeup(),__wakeup()內部全都不會執行啦 不信你可以試試
)
這就是一個非常典型的反序列化漏洞
其他魔術方法同理
<?php class Test1{ function __construct($test){ $fp = fopen("shell.php", "w"); fwrite($fp, $test); fclose($fp); } } class Test2{ var $test = "123"; function __wakeup(){ $obj = new Test1($this -> test); } } $test = $_GET['test']; unserialize($test); require "shell.php"; ?>
再比如上邊這個__construct()
與第一個例子用 一樣的payload 一樣的感覺
其實真正造成危險的代碼在魔術方法中,上面兩個例子用了新建文件操作,后面還有文件包含操作,由於魔術方法往往伴隨某些條件自動調用,所以。。。
那么是不是說一些執行操作的代碼片段不放在魔術方法中就安全了,也不一定
一些方法互相調用,可能調用來調用去,還是會執行某些操作
總結一下PHP反序列化漏洞出現的原因:
1.unserialize()傳入參數可控
2.存在某些魔術方法可用
3.沒有過濾或者過濾不完善
一般有反序列化函數內部都有 正則過濾(淚目)或者啥過濾,沒有就會給構造payload繞過以可乘之機
0x02 Java反序列化漏洞
有兩個類:
Java.io.ObjectOutputStream
Java.io.ObjectInputStream
這倆是Java中實現序列化與反序列化的關鍵類
序列化用到了ObjectOutputStream
類中的writeObject()
反序列化用到了ObjectInputStream
類中的readObject()
借大佬的代碼舉個例子
import java.io.*; /* import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.FileOutputStream; import java.io.FileInputStream; */ public class Test{ public static void main(String args[]) throws Exception { String obj = "helloworld"; // 將序列化對象寫入文件中 FileOutputStream fos = new FileOutputStream("lcx.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); os.close(); // 從文件中讀取數據 FileInputStream fis = new FileInputStream("lcx.ser"); ObjectInputStream ois = new ObjectInputStream(fis); // 通過反序列化恢復對象 String obj2 = (String)ois.readObject(); System.out.println(obj2); ois.close(); } }
創建文件,把序列化數據寫入文件,讀取文件,反序列化數據,打印數據
(實現Serializable和Externalizable接口的類的對象才能被序列化)
對象序列化包括如下步驟:
1) 創建一個對象輸出流,它可以包裝一個其他類型的目標輸出流,如文件輸出流;
2) 通過對象輸出流的writeObject()方法寫對象
對象反序列化的步驟如下:
1) 創建一個對象輸入流,它可以包裝一個其他類型的源輸入流,如文件輸入流;
2) 通過對象輸入流的readObject()方法讀取對象
更進一步,把文件變成類
import java.io.*; public class test{ public static void main(String args[]) throws Exception{ UnsafeClass Unsafe = new UnsafeClass(); Unsafe.name = "my name is lcx"; FileOutputStream fos = new FileOutputStream("object"); ObjectOutputStream os = new ObjectOutputStream(fos); //writeObject()方法將Unsafe對象寫入object文件 os.writeObject(Unsafe); os.close(); //從文件中反序列化obj對象 FileInputStream fis = new FileInputStream("object"); ObjectInputStream ois = new ObjectInputStream(fis); //恢復對象 UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject(); System.out.println(objectFromDisk.name); ois.close(); } } class UnsafeClass implements Serializable{ public String name; //重寫readObject()方法 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ //執行默認的readObject()方法 in.defaultReadObject(); //執行命令 Runtime.getRuntime().exec("calc.exe"); } }
UnsafeClass實例化的對象被序列化進object文件
再被讀取並反序列化
執行了重寫后的readObject方法,顯示 my name is lcx 並彈出了計算器
Java反序列化漏洞和所有反序列化漏洞一樣,都是由於對用戶提供的、不可信的數據進行了反序列化處理,沒做任何處理產生了非預期對象,帶來了某些代碼執行
有幾個經典的例子:Apache Commons Collections序列化RCE漏洞、Spring框架反序列化漏洞、Fastjson反序列化漏洞、 Apache Shiro Java 反序列化漏洞(具體的不展開了,網上某某社區也有不少大佬寫的poc很好很全面,Java玩的不好就不班門弄斧誤人子弟了)
反序列化操作一般在導入模版文件、網絡通信、數據傳輸、日志格式化存儲、對象數據落磁盤或DB存儲等業務場景,在代碼審計時可重點關注一些反序列化操作函數並判斷輸入是否可控,如
readObject()
方法是否重寫,重寫中是否有設計不合理,可以被利用之處),看是不是有危險庫,有的話利用相關方法搞一搞
)開頭。 Java RMI 的傳輸 100% 基於反序列化,Java RMI 的默認端口是
1099
端口,有大佬寫的腳本對1099端口進行掃描攻擊
2020網鼎杯 朱雀組Web 之 think java