序列化
所有php里面的值都可以使用函數serialize()來返回一個包含字節流的字符串來表示。unserialize()函數能夠重新把字符串變回php原來的值。 序列化一個對象將會保存對象的所有變量,但是不會保存對象的方法,只會保存類的名字。 --php官方文檔
魔術方法
構造函數和析構函數
- __construct()
具有構造函數的類會在每次創建新對象時先調用此方法,所以非常適合在使用對象之前做一些初始化工作。 - __destruct()
析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行。
也就是說進行反序列化時,完成的就是從字符串創建新對象的過程,剛開始就會調用__construct(),而對象被銷毀時,例如程序退出時,就會調用__destruct()
__sleep()
和__wakeup()
__toString()
echo
或者拼接字符串或者其他隱式調用該方法的操作都會觸發
__set()
, __get()
, __isset()
, __unset()
__invoke()
, __call()
當嘗試以調用函數的方式調用一個對象時,__invoke()
方法會被自動調用。
在對象中調用一個不可訪問方法時,__call()
會被調用。
其他
__callStatic(), __set_state(), __clone(), __debugInfo()等和序列化沒有多大關系,詳情參考官網
序列化細節
序列的含義
例如:O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"LEO";}
O代表對象;4代表對象名長度;2代表2個成員變量;其余參照如下
類型 | 結構 |
---|---|
String | s:size:value; |
Integer | i:value; |
Boolean | b:value;(保存1或0) |
Null | N; |
Array | a:size:{key definition;value definition;(repeated per element)} |
Object | O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)} |
public、protected、private下序列化對象的區別
php v7.x反序列化的時候對訪問類別不敏感
- public變量
直接變量名反序列化出來 - protected變量
\x00 + * + \x00 + 變量名
可以用S:5:"\00*\00op"
來代替s:5:"?*?op"
- private變量
\x00 + 類名 + \x00 + 變量名
反序列化的利用
-
__wakeup失效
php版本< 5.6.25 | < 7.0.10
當序列化字符串中,如果表示對象屬性個數的值大於真實的屬性個數時就會跳過__wakeup()的執行
例:O:4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";} O:4:"Demo":2:{s:10:"Demofile";s:16:"f15g_1s_here.php";}
-
使用
+
繞過正則
例:preg_match('/[oc]:\d+:/i', $var) O:4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";} O:+4:"Demo":1:{s:10:"Demofile";s:16:"f15g_1s_here.php";}
Session序列化問題
https://bugs.php.net/bug.php?id=71101
PHP內置了多種處理器用於存儲$_SESSION數據時會對數據進行序列化和反序列化,常用的有以下三種,對應三種不同的處理格式:
處理器 | 對應的存儲格式 |
---|---|
php | 鍵名 + 豎線 + 經過serialize()函數反序列化處理的值 |
php_binary | 鍵名的長度對應的ASCII字符 + 鍵名 + 經過serialize()函數反序列化處理的值 |
php_serialize(php>=5.5.4) | 經過serialize()函數反序列處理的數組 |
配置選項 session.serialize_handler,通過該選項可以設置序列化及反序列化時使用的處理器,
ini_set('session.serialize_handler', 'php_binary')
默認為php。
如果PHP在反序列化存儲的$_SEESION數據時的使用的處理器和序列化時使用的處理器不同,會導致數據無法正確反序列化,通過特殊的偽造,甚至可以偽造任意數據。
當存儲是php_serialize處理,然后調用時php去處理,如果這時注入的數據時a=|O:4:"test":0:{}
,那么session中的內容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
,那么a:1:{s:1:"a";s:16:"
會被php解析成鍵名,后面就是一個test對象的注入
當配置選項session.auto_start=Off
,兩個腳本注冊session繪畫是使用的序列化處理器不同,就會出現安全問題
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />
<input type="file" name="file" />
<input type="submit" />
</form>
The key of stored in the session will look like this:
$_SESSION["upload_progress_ryat"]
ryat
的部分可以注入自己的代碼,如ryat|序列化字符串
Phar反序列化
https://blog.ripstech.com/2018/new-php-exploitation-technique/
PHAR反序列化拓展操作總結
SUCTF 2019 出題筆記 & phar 反序列化的一些拓展
利用 phar 拓展 php 反序列化漏洞攻擊面
創建一個Phar文件
// create new Phar
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object->data = 'rips';
$phar->setMetadata($object);
$phar->stopBuffering();
如果現在通過phar://包裝器對我們現有的Phar文件執行文件操作,則其序列化元數據將被反序列化。這意味着我們在元數據中注入的對象被加載到應用程序的范圍中。如果此應用程序具有已命名的類AnyClass並且具有魔術方法__destruct()或已__wakeup()定義,則會自動調用這些方法。這意味着我們可以在代碼庫中觸發任何析構函數或喚醒方法。更糟糕的是,如果這些方法對我們注入的數據進行操作,那么這可能會導致進一步的漏洞。
//通過Phar文件進行PHP對象注入
class AnyClass {
function __destruct() {
echo $this->data;
}
}
// output: rips
include('phar://test.phar');
phar://
在任何文件操作中都會為包裝器觸發unserialize,可利用函數包括
ìnclude($_GET['file']);
fopen($_GET['file']);
file_get_contents($_GET['file']);
file($_GET['file']);
file_exists($_GET['file']);
md5_file($_GET['file']);
filemtime($_GET['file']);
filesize($_GET['file']);
當對上傳文件內容有過濾時,可以用gzip
壓縮來消除敏感字符
例題:[hitcon2017] Baby^H-master-php-2017 復現
PS:匿名函數的真正名字為:%00lambda_%d(%d格式化為當前進程的第n個匿名函數)