在本節中,咱們將介紹什麼是不安全的反序列化,並描述它是如何使網站遭受高危害性攻擊的。咱們將重點介紹典型的場景,並演示一些 PHP、Ruby 和 Java 反序列化的具體示例。最後也會介紹一些避免不安全的反序列化漏洞的方法。java
利用不安全的反序列化一般比較困難。然而,它有時比你想象的要簡單得多。若是您不熟悉反序列化,那麼本節將包含一些重要的背景信息,您應該首先熟悉這些信息。若是您已經瞭解反序列化的基礎知識,那麼能夠直接跳到學習如何利用它。數據庫
序列化是將複雜的數據結構(如對象及其字段)轉換爲「更扁平」的格式的過程,該格式的數據能夠做爲字節流序列發送和接收。序列化的數據讓如下過程更簡單:編程
關鍵的是,當序列化一個對象時,其狀態也將保持不變。換句話說,對象的屬性及其賦值都會被保留。安全
反序列化是將字節流還原爲與原始對象徹底相同的副本的過程。而後,網站的邏輯就能夠與這個反序列化的對象進行交互,就像與任何其餘對象進行交互同樣。服務器
許多編程語言提供對序列化的本地支持。具體如何序列化對象取決於具體語言。一些語言將對象序列化爲二進制格式,而另外一些語言則會序列化爲具備不一樣程度的可讀性的字符串格式。請注意,原始對象的全部屬性都存儲在序列化數據流中,包括全部私有字段。爲了防止字段被序列化,必須在類聲明中將其顯式標記爲"transient" 。cookie
請注意,當使用不一樣的編程語言時,序列化可能被稱爲 marshalling(Ruby)或 pickling(Python),這些術語與「序列化」同義。網絡
不安全的反序列化是指用戶可控制的數據被網站反序列化。這會使攻擊者可以操縱序列化的對象,以便將有害數據傳遞到應用程序代碼中。數據結構
甚至能夠用徹底不一樣類的對象替換序列化對象。使人擔心的是,網站上任何可用的類的對象都將被反序列化和實例化,而無論這個類是否是預期的類。所以,不安全的反序列化有時被稱爲 "object injection" 對象注入漏洞。框架
一個意外類的對象可能會致使異常。不過,在此以前,損害可能已經形成。許多基於反序列化的攻擊在反序列化結束以前就已經完成。這意味着能夠攻擊反序列化的過程自己,即便網站的功能不直接與惡意對象交互。所以,其邏輯基於強類型語言的網站也容易受到這些技術的攻擊。編程語言
不安全的反序列化出現一般是由於人們廣泛缺少對用戶可控數據進行反序列化的危險程度的瞭解。理想狀況下,根本不該該對用戶輸入進行反序列化。
有些網站全部者認爲他們很安全,由於其會對反序列化的數據進行某種形式的附加檢查。然而,這種方法一般是無效的,由於幾乎不可能驗證或預料到全部可能發生的狀況。這些檢查在根本上也是有缺陷的,由於它們依賴於在數據被反序列化後對其進行檢查,在許多狀況下,這對於防止攻擊來講已經太晚了。
漏洞產生也多是由於反序列化的對象一般被認爲是可信的。特別是在使用二進制序列化格式的語言時,開發人員可能會認爲用戶沒法有效地讀取或操做數據。然而,儘管這可能須要更多努力,但攻擊者利用二進制序列化對象的可能性與利用基於字符串的格式的可能性是同樣的。
因爲現代網站中存在大量依賴項,所以基於反序列化的攻擊也成爲可能。一個站點可能使用許多不一樣的庫,每一個庫也都有本身的依賴項,這就產生了一個難以安全管理的存在大量類和方法的池子。因爲攻擊者能夠建立這些類中的任何一個實例,所以很難預測能夠對惡意數據調用哪些方法。若是攻擊者可以將一長串意外的方法調用連接在一塊兒,並將數據傳遞到與初始源徹底無關的接收器中,則尤爲如此。所以,幾乎不可能預測到惡意數據的流動並堵塞每一個潛在的漏洞。
簡而言之,安全地反序列化不受信任的輸入是不可能的。
不安全的反序列化的影響可能很是嚴重,由於它提供了一個切入點,從而致使攻擊面大幅增長。它容許攻擊者以有害的方式重用現有的應用程序代碼,從而致使許多其餘漏洞,好比遠程代碼執行.
即便在沒法執行遠程代碼的狀況下,不安全的反序列化也可能致使權限提高、訪問任意文件和拒絕服務攻擊。
下文會有詳細說明。
通常來講,除非絕對必要,不然應該避免用戶輸入的反序列化。在許多狀況下,防護其潛在的高危漏洞的難度超過了其帶來的好處。
若是確實須要反序列化來自不受信任的源的數據,請採起強大的措施以確保數據未被篡改。例如,您能夠實現一個數字簽名來檢查數據的完整性。可是,請記住,任何檢查都必須在開始反序列化以前進行。不然,檢查就沒什麼用處了。
若是可能,您應該避免使用通用的反序列化功能。這些方法的序列化數據包含了原始對象的全部屬性,以及可能包含敏感信息的私有字段。相反,你應該建立本身特定類的序列化方法,以控制公開字段。
最後,請記住,該漏洞是用戶輸入的反序列化,而不是隨後處理數據的工具鏈的存在。不要依賴於試圖消除測試過程當中識別的工具鏈,因爲跨庫依賴的存在,這是不切實際的。在任何給定的時間,公開記錄的內存損壞漏洞也意味着應用程序可能會受到攻擊。
在本節中,咱們將經過 PHP、Ruby 和 Java 反序列化的示例來教你如何利用一些常見漏洞場景。咱們但願證實利用不安全的反序列化實際上比許多人認爲的要容易得多。若是你可以使用預先構建的工具鏈,那麼即便在黑盒測試期間也是如此。
咱們還將指導你建立基於反序列化高危漏洞的攻擊。儘管這些一般須要訪問源代碼,可是一旦理解了基本概念,它們也比你想象的更容易學習。咱們將討論如下主題:
注意:儘管許多實驗和示例都基於PHP,但大多數開發技術對其餘語言也一樣有效。
識別不安全的反序列化相對來講比較簡單,不管你使用白盒測試仍是黑盒測試。
在審覈過程當中,你應該查看網站全部傳入數據,並嘗試識別出任何相似於序列化的數據。若是你知道不一樣語言使用的格式,則能夠相對容易地識別序列化的數據。在本節中,咱們將展現 PHP 和 Java 序列化的示例。一旦肯定了序列化的數據,就能夠測試是否可以控制它。
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
- 第二個屬性的值是布爾值 truePHP 序列化的本地方法是 serialize()
和 unserialize()
。若是你有源代碼的訪問權限,則應該首先在全部位置查找 unserialize()
並進行進一步調查。
有些語言,如 Java ,使用二進制序列化格式。這更難閱讀,但若是知道如何識別一些信號,則仍然能夠識別序列化的數據。例如,序列化的 Java 對象老是以相同的字節開頭,這些字節被編碼爲十六進制 ac ed
和 Base64 的 rO0
。
實現接口 java.io.Serializable
的任何類均可以序列化和反序列化。若是你有源代碼的訪問權限,請注意使用 readObject()
的方法,該方法用於從 InputStream 中讀取並反序列化數據。
利用某些反序列化漏洞就像更改序列化對象中的屬性同樣容易。當對象狀態被持久化時,你能夠研究序列化數據以識別和編輯感興趣的屬性值。而後,經過反序列化過程將惡意對象傳遞給網站。這是基本的反序列化攻擊的初始步驟。
廣義地說,在操縱序列化對象時能夠採用兩種方法。你能夠直接以字節流的形式編輯對象,也能夠用相應的語言編寫一個簡短的腳原本本身建立和序列化新對象。使用二進制序列化格式時,後一種方法一般更容易。
在篡改數據時,只要攻擊者保留一個有效的序列化對象,反序列化過程將使用修改後的屬性值建立一個服務器端的對象。
做爲一個簡單的示例,假設一個網站使用序列化對象 User
在 cookie 中存儲有關用戶會話的數據。若是攻擊者在 HTTP 請求中發現了這個序列化對象,他們可能會對其進行解碼以找到如下字節流:
O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}
這個 isAdmin
屬性很容易引發攻擊者的興趣。攻擊者只需將這個屬性的布爾值更改成 1(true),而後從新編碼對象,並用此修改後的值覆蓋當前 cookie 。單獨來看的話這沒啥用。可是,若是網站使用此 cookie 檢查當前用戶是否有權訪問某些管理功能:
$user = unserialize($_COOKIE); if ($user->isAdmin === true) { // allow access to admin interface }
上述代碼將基於來自 cookie 的數據實例化 User 對象,包括攻擊者修改後的 isAdmin
屬性,而且不會檢查序列化對象的真實性。此時,修改後的數據就直接升級了權限。
這種簡單的場景並不常見。然而,以這種方式編輯屬性值展現了進行攻擊的第一步。
咱們除了修改序列化對象中的屬性值以外,也能夠提供意外的數據類型。
像 PHP 這種弱類型語言,使用鬆散的比較運算符 == 比較不一樣的數據類型時特別容易受到這種操做的攻擊。例如,若是在整數和字符串之間執行鬆散比較,PHP 將嘗試將字符串轉換爲整數,這意味着 5 == "5"
計算結果爲 true.
特別的是,這也適用於任何以數字開頭的字母數字字符串。PHP 會將整個字符串轉換爲初始數字的整數值,字符串的其他部分將被徹底忽略。所以,5 == "5 of something"
實際上被視爲 5 == 5
。
當將字符串與整數 0 進行比較時,這變得更加奇怪:
0 == "Example string" // true
由於字符串中沒有數字,PHP 會將整個字符串視爲整數 0 。
考慮這樣一種狀況:將這個鬆散的比較運算符與來自反序列化對象的用戶可控數據一塊兒使用,這可能致使危險的邏輯缺陷。
$login = unserialize($_COOKIE) if ($login['password'] == $password) { // log in successfully }
假設攻擊者修改了 password 屬性,使其爲整數 0 而不是預期的字符串。那麼只要存儲的密碼不是以數字開頭,就會致使身份驗證經過。請注意,這只是一種可能性,由於反序列化可保留數據類型,若是代碼直接從請求中獲取密碼,則 0 將轉換爲字符串,而且條件的評估結果爲 false 。
請注意,在修改任何序列化對象格式的數據類型時,務必記住也要更新序列化數據中的任何類型標籤和長度指示符。不然,序列化的對象將損壞,而且不會反序列化。
當直接使用二進制格式時,咱們建議使用 Hackvertor 擴展,其能夠從 BApp store 中得到。使用 Hackvertor,你能夠將序列化數據修改成字符串,它將自動更新二進制數據,並相應地調整偏移量,這能夠節省大量的手動操做。
除了簡單地檢查屬性值以外,網站的功能還可能對反序列化對象中的數據執行危險的操做。在這種狀況下,你可使用不安全的反序列化來傳遞意外的數據,並利用相關功能形成損害。
例如,做爲網站「刪除用戶」功能的一部分,經過訪問 $user->image_location
屬性能夠刪除用戶的我的資料圖片。若是這個 $user
來源於序列化對象,則攻擊者能夠經過傳入一個修改了 image_location
的對象將其設置爲任意一個文件路徑。刪除他們本身的用戶賬戶也會刪除這個任意文件。
此示例依賴於攻擊者經過用戶可訪問的功能手動調用危險方法。然而,當你構造將數據自動傳遞到危險方法的漏洞利用時,不安全的反序列化將變得更加有趣。這是經過使用「魔術方法」來實現的。
魔術方法是沒必要顯式調用的方法的特殊子集。相反,它們會在特定事件或場景發生時自動調用。魔術方法是各類語言中面向對象編程的一個共同特徵。它們有時經過在方法名前面加上前綴或用雙下劃線包圍來表示。
開發人員能夠向類中添加魔術方法,以便預先肯定在相應的事件或場景發生時應該執行哪些代碼。調用魔術方法的確切時間和緣由因方法而異。PHP 中最多見的例子之一是 __construct()
,其在實例化類的對象時調用,相似於 Python 的 __init__
。 一般,像這樣的構造函數魔術方法包含初始化實例屬性的代碼。然而,開發人員能夠自定義魔術方法來執行他們想要的任何代碼。
魔術方法被普遍使用,其自己並不表明漏洞。但當它們執行的代碼對攻擊者可控制的數據(例如,來自反序列化對象的數據)進行處理時,它們可能變得危險。攻擊者可利用此漏洞在知足相應條件時自動調用反序列化數據上的方法。
在這種狀況下,最重要的是,某些語言具備在反序列化過程當中自動調用的魔術方法。例如,PHP 的 unserialize()
方法查找並調用對象的 __wakeup()
神奇的方法。
在 Java 反序列化中,這一樣適用於 readObject()
方法,它本質上相似於「從新初始化」序列化對象的構造函數。這個 ObjectInputStream.readObject()
方法用於從初始字節流中讀取數據。可是,可序列化類也能夠聲明本身的 readObject()
方法以下:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {...};
這使得類能夠更緊密地控制本身字段的反序列化。最重要的是以這種方式聲明的 readObject()
方法充當了在反序列化期間調用的魔術方法。
你應該密切關注包含這類魔術方法的任何類。它們容許你在對象徹底反序列化以前,將數據從序列化對象傳遞到網站代碼中。這是利用更高級漏洞的起點。
正如咱們所看到的,偶爾能夠經過編輯網站提供的對象來利用不安全的反序列化。然而,注入任意對象能夠帶來更多的可能性。
在面向對象編程中,對象可用的方法由其類決定。所以,若是攻擊者能夠操縱做爲序列化數據傳入的對象類,則能夠影響反序列化以後,甚至在反序列化期間執行的代碼。
反序列化方法一般不檢查反序列化的內容。這意味着你能夠傳入網站可用的任何可序列化類的對象,而且該對象將被反序列化。這容許了攻擊者建立任意類的實例。該對象不是預期類的事實並不重要。意外的對象類型可能會致使應用程序邏輯中的異常,可是惡意對象已經實例化。
若是攻擊者有權訪問源代碼,他們能夠詳細研究全部可用的類。爲了構造一個簡單的攻擊,他們會尋找包含反序列化魔術方法的類,而後檢查其中是否有任何類對可控數據執行危險操做。而後,攻擊者將會傳入這個類的序列化對象,以使用其魔術方法進行攻擊。
包含這些反序列化魔術方法的類也可用於發起更復雜的攻擊,這涉及一系列的方法調用,稱爲 "gadget chain" 調用鏈。
"gadget" 是應用程序中存在的一段代碼,能夠幫助攻擊者實現特定目標。單個 gadget 不能直接對用戶輸入形成任何有害影響。然而,攻擊者的目標可能只是調用一個將其輸入傳遞到另外一個 gadget 的方法。經過以這種方式將多個 gadget 連接在一塊兒,攻擊者可能會將他們的輸入傳遞到一個危險的 "sink gadget",從而形成最大的破壞。
重要的是要了解,與其餘類型的攻擊不一樣,gadget 鏈不是攻擊者構建的鏈式方法的有效負載。全部的代碼都已經存在於網站上。攻擊者惟一控制的是傳遞到 gadget 鏈中的數據。這一般反序列化期間調用魔術方法來完成,有時稱爲「啓動gadget」。
許多不安全的反序列化漏洞只能經過使用gadget鏈來利用。這有時多是一個簡單的一步或兩步鏈,但構建高危害性攻擊可能須要更精細的對象實例化和方法調用序列。所以,可以構造 gadget 鏈是成功利用不安全反序列化的關鍵因素之一。
手動識別 gadget 鏈多是一個至關艱鉅的過程,若是沒有源代碼訪問,幾乎不可能。幸運的是,有一些方法能夠用來處理預先構建的 gadget 鏈,你能夠先嚐試一下。
有幾種可用的工具能夠幫助你以最小的工做量構建 gadget 鏈。這些工具提供了一系列預先發現的 gadget 鏈,這些 gadget 鏈在其餘網站上被利用過。在目標站點上發現了不安全的反序列化漏洞後,即便你無權訪問源代碼,也可使用這些工具嘗試並利用它。因爲包含可利用 gadget 鏈的庫的普遍使用,這種方法成爲可能。例如,若是一個依賴 Java 的 ApacheCommons Collections 庫的 gadget 鏈能夠在某個網站上被利用,那麼使用該庫的任何其餘網站也可使用同一個鏈進行攻擊。
其中一個用於 Java 反序列化的工具是 "ysoserial" 。你只需指定一個你認爲目標應用程序正在使用的庫,而後提供一個要嘗試並執行的命令,該工具就會根據已知的給定庫的 gadget 鏈建立適當的序列化對象。這仍然須要必定量的嘗試,但它比手工構建本身的 gadget 鏈要輕鬆得多。
大多數常常遭受不安全反序列化攻擊的語言都有匹配的 proof-of-concept 工具。例如,對於基於 PHP 的站點,可使用 "PHP Generic Gadget Chains"(PHPGGC)。
須要注意的是,網站代碼或其任何庫中存在的 gadget 鏈並非致使該漏洞的緣由。該漏洞是用戶可控制數據的反序列化,gadget 鏈只是在數據被注入後操縱數據流的一種手段。這也適用於依賴於非可信數據反序列化的各類內存破壞漏洞。所以,即便他們設法管理每個可能插入的 gadget 鏈,網站可能仍然是脆弱的。
你能夠看看是否有任何記錄在案的漏洞利用,能夠拿來攻擊你的目標網站。即便沒有用於自動生成序列化對象的專用工具,你仍然能夠爲流行框架找到有記錄的 gadget 鏈並手動調整它們。
若是你找不到一個可使用的 gadget 鏈,你仍然能夠得到有價值的知識,你能夠利用這些知識建立本身的自定義漏洞利用程序。
當現成的 gadget 鏈和有記錄的漏洞攻擊不成功時,你須要建立本身的漏洞利用。
爲了成功地構建本身的 gadget 鏈,你幾乎確定須要訪問源代碼。第一步是研究此源代碼,以識別包含反序列化期間調用的魔術方法的類。評估這個魔術方法執行的代碼,看看它是否直接使用用戶可控制的屬性作任何危險的事情。
若是魔術方法自己不可利用,它能夠做爲你的 gadget 鏈的啓動點。研究啓動 gadget 調用的任何方法。這些操做是否會對你控制的數據形成危險?若是不是,請仔細查看它們隨後調用的每一個方法,依此類推。
重複此過程,跟蹤你能夠訪問的值,直到你到達死衚衕或識別出一個危險的 sink gadget ,你的可控數據被傳遞到其中。
一旦解決了如何在應用程序代碼中成功地構造 gadget 鏈,下一步就是建立一個包含有效負載的序列化對象。這隻需研究源代碼中的類聲明並建立一個有效的序列化對象,該對象具備利用漏洞所需的適當值。正如咱們在之前的實驗室中看到的,使用基於字符串的序列化格式時,這一點相對簡單。
使用二進制格式,例如在構建 Java 反序列化漏洞時,可能會特別麻煩。在對現有對象進行小的更改時,直接使用字節可能會很舒服。可是,當進行更重要的更改時,例如傳入一個全新的對象,這很快就變得不切實際了。爲了本身生成和序列化數據,用目標語言編寫本身的代碼一般要簡單得多。
在建立本身的 gadget 鏈時,要注意利用這個額外的攻擊面觸發次要漏洞的機會。
經過仔細研究源代碼,你能夠發現更長的 gadget 鏈,這些 gadget 鏈可能容許你構建高危險性攻擊,一般包括遠程代碼執行.
到目前爲止,咱們主要研究瞭如何利用反序列化漏洞,即網站顯式地反序列化用戶輸入。然而,在 PHP 中,有時即便沒有明顯使用 unserialize() 方法,也有可能能夠利用反序列化漏洞。
當你訪問不一樣的文件時,PHP 提供了不一樣的處理方式。其中之一是 phar://
,它提供了一個流式接口來訪問 PHP Archive (.phar) 文件。
PHP 文檔揭示了 PHAR 清單文件包含序列化的元數據。相當重要的是,若是你對 phar://
流執行文件系統操做,其元數據會被隱式的反序列化。這意味着 phar://
流多是利用不安全的反序列化的潛在點,前提是能夠將此流傳遞到文件系統方法中。
對於明顯危險的文件系統方法,例如 include()
或 fopen()
,網站極可能已經實施了反制措施,以減小它們被惡意使用的可能性。然而,諸如 file_exists()
這類看起來沒有明顯危險的方法可能沒有獲得很好的保護。
此技術要求你經過某種方式將 PHAR 上傳到服務器。例如,一種方法是使用圖像上傳功能。若是你可以將 PHAR 假裝成一個簡單的 JPG 文件,你有時能夠繞過網站的驗證檢查。若是你能強迫網站加載這個假裝成 JPG 的 PHAR 流,則任何經過 PHAR 元數據注入的有害數據都將被反序列化。因爲 PHP 讀取流時不檢查文件擴展名,所以文件是否使用圖像擴展名並不重要。
只要對象的類是由網站支持的,則 __wakeup()
和 __destruct()
魔術方法能夠用這種方式調用,從而容許你使用這種技術啓動一個 gadget 鏈。
即便不使用 gadget 鏈,也有可能利用不安全的反序列化。若是全部其餘方法都失敗,一般會有公開記錄的內存損壞漏洞,能夠經過不安全的反序列化來利用這些漏洞。這些一般會致使遠程代碼執行。
反序列化方法,例如 PHP 的 unserialize()
不多對這類攻擊進行強化,暴露出大量的攻擊面。其自己並不總會被認爲是一個漏洞,由於這些方法一開始並不打算處理用戶可控制的輸入。