剖析Revive Adserver用户名验证绕过漏洞与安全修复方案

本文详细分析了Revive Adserver中一个用户名验证绕过漏洞,该漏洞允许攻击者通过Unicode特殊字符(如零宽空格、RTL覆盖符、西里尔文同形字)创建仿冒账户。报告包含详细的漏洞细节、可复现的POC代码、完整的测试脚本以及将黑名单模式替换为白名单的根治性修复建议。

Revive Adserver | 报告 #3434156 - 用户名验证绕过 | HackerOne

执行摘要 提交d239a0845e4f64fbacd25fff2854426734d43aa2中的安全补丁不充分。 测试证实,4种攻击向量中有3种仍然可以绕过验证。

漏洞详情 受影响组件:用户注册/创建中的用户名验证 文件:lib/OA/Admin/UI/UserAccess.php 行号:116 当前模式:#[\x00-\x1F\x7F\s]#u 严重性:中-高 (CVSS 6.5) 影响:账户冒充、钓鱼攻击、验证绕过

概念验证 - 有效的利用方法 利用方法1:零宽空格 (U+200B) CRITICAL 描述:不可见的Unicode字符,可创建重复账户 Payload (十六进制):61646d696ee2808b Payload (解码):admin + 0xE2 0x80 0x8B (U+200B的UTF-8编码) Payload (字面):用户名 "admin" 后跟Unicode字符 U+200B 如何复现:

1
2
3
$malicious_username = "admin" . "\xE2\x80\x8B"; // 十六进制字节
// 或
$malicious_username = "admin" . mb_chr(0x200B, 'UTF-8'); // Unicode码点

测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
$username = "admin" . "\xE2\x80\x8B"; // admin + 零宽空格

echo "Username: admin[U+200B]\n";
echo "Hex bytes: " . bin2hex($username) . "\n";
echo "Expected: 61646d696ee2808b\n\n";

// 针对已修补的模式进行测试
if (preg_match('#[\x00-\x1F\x7F\s]#u', $username)) {
 echo "Result: BLOCKED ✓\n";
} else {
 echo "Result: PASSES - VULNERABLE! ✗\n";
}
?>

预期输出:

1
2
3
4
5
Username: admin[U+200B]
Hex bytes: 61646d696ee2808b
Expected: 61646d696ee2808b

Result: PASSES - VULNERABLE! ✗

影响:

  • 创建视觉上与"admin"完全相同的账户
  • 绕过用户名唯一性约束
  • 支持账户枚举攻击
  • 非常适合冒充攻击

利用方法2:从右向左覆盖符 (U+202E) HIGH 描述:使文本显示方向反转的右向左覆盖标记 Payload (十六进制):61646d696ee280ae74657374 Payload (解码):admin + 0xE2 0x80 0xAE + test Payload (字面):文本 "admin" + U+202E + "test" 如何复现:

1
2
3
$malicious_username = "admin" . "\xE2\x80\xAE" . "test"; // 十六进制字节
// 或
$malicious_username = "admin" . mb_chr(0x202E, 'UTF-8') . "test"; // Unicode

测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
$username = "admin" . "\xE2\x80\xAE" . "test"; // admin + RTL覆盖符 + test

echo "Username: admin[U+202E]test\n";
echo "Hex bytes: " . bin2hex($username) . "\n";
echo "Expected: 61646d696ee280ae74657374\n";
echo "Visual display: 'tset' + 'admin' (reversed!)\n\n";

// 针对已修补的模式进行测试
if (preg_match('#[\x00-\x1F\x7F\s]#u', $username)) {
 echo "Result: BLOCKED ✓\n";
} else {
 echo "Result: PASSES - VULNERABLE! ✗\n";
}
?>

预期输出:

1
2
3
4
5
6
Username: admin[U+202E]test
Hex bytes: 61646d696ee280ae74657374
Expected: 61646d696ee280ae74657374
Visual display: 'tset' + 'admin' (reversed!)

Result: PASSES - VULNERABLE! ✗

影响:

  • 文本在UI中显示为反转("testadmin"显示顺序不同)
  • 使审查用户账户的管理员感到困惑
  • 支持社会工程学攻击
  • 在日志和管理面板中实现视觉欺骗

利用方法3:西里尔文同形字 (U+0430) CRITICAL 描述:与拉丁字母 'a' (U+0061) 外观相同的西里尔字母 'а' (U+0430) Payload (十六进制):d0b0646d696e Payload (解码):0xD0 0xB0 + dmin Payload (字面):西里尔字母 'а' (U+0430) 后跟拉丁字母 "dmin" 如何复现:

1
2
3
$malicious_username = "\xD0\xB0" . "dmin"; // 十六进制字节
// 或
$malicious_username = mb_chr(0x0430, 'UTF-8') . "dmin"; // Unicode

测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
$username = "\xD0\xB0" . "dmin"; // 西里尔文 а + 拉丁文 dmin

echo "Username: [U+0430]dmin (looks like 'admin')\n";
echo "Hex bytes: " . bin2hex($username) . "\n";
echo "Expected: d0b0646d696e\n";
echo "Visual: Appears identical to 'admin'\n\n";

// 与真实的 admin 对比
$real_admin = "admin";
echo "Real 'admin' hex: " . bin2hex($real_admin) . "\n";
echo "Looks same? YES | Hex same? NO\n\n";

// 针对已修补的模式进行测试
if (preg_match('#[\x00-\x1F\x7F\s]#u', $username)) {
 echo "Result: BLOCKED ✓\n";
} else {
 echo "Result: PASSES - VULNERABLE! ✗\n";
}
?>

预期输出:

1
2
3
4
5
6
7
8
9
Username: [U+0430]dmin (looks like 'admin')
Hex bytes: d0b0646d696e
Expected: d0b0646d696e
Visual: Appears identical to 'admin'

Real 'admin' hex: 61646d696e
Looks same? YES | Hex same? NO

Result: PASSES - VULNERABLE! ✗

完整的测试脚本 保存为 test_vulnerability.php 并用 php test_vulnerability.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
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
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

echo "========================================\n";
echo "Revive Adserver - Vulnerability Test\n";
echo "========================================\n\n";

// Test 1: Zero-Width Space
echo "[TEST 1] Zero-Width Space (U+200B)\n";
echo "----------------------------------------\n";
$test1 = "admin" . "\xE2\x80\x8B";
echo "Payload hex: " . bin2hex($test1) . "\n";
echo "Expected:    61646d696ee2808b\n";
echo "Match: " . (bin2hex($test1) === "61646d696ee2808b" ? "YES ✓" : "NO ✗") . "\n";
$blocked1 = preg_match('#[\x00-\x1F\x7F\s]#u', $test1);
echo "Status: " . ($blocked1 ? "BLOCKED ✓" : "VULNERABLE ✗") . "\n\n";

// Test 2: RTL Override
echo "[TEST 2] RTL Override (U+202E)\n";
echo "----------------------------------------\n";
$test2 = "admin" . "\xE2\x80\xAE" . "test";
echo "Payload hex: " . bin2hex($test2) . "\n";
echo "Expected:    61646d696ee280ae74657374\n";
echo "Match: " . (bin2hex($test2) === "61646d696ee280ae74657374" ? "YES ✓" : "NO ✗") . "\n";
$blocked2 = preg_match('#[\x00-\x1F\x7F\s]#u', $test2);
echo "Status: " . ($blocked2 ? "BLOCKED ✓" : "VULNERABLE ✗") . "\n\n";

// Test 3: Cyrillic Homograph
echo "[TEST 3] Cyrillic Homograph (U+0430)\n";
echo "----------------------------------------\n";
$test3 = "\xD0\xB0" . "dmin";
echo "Payload hex: " . bin2hex($test3) . "\n";
echo "Expected:    d0b0646d696e\n";
echo "Match: " . (bin2hex($test3) === "d0b0646d696e" ? "YES ✓" : "NO ✗") . "\n";
$blocked3 = preg_match('#[\x00-\x1F\x7F\s]#u', $test3);
echo "Status: " . ($blocked3 ? "BLOCKED ✓" : "VULNERABLE ✗") . "\n\n";

// Test 4: Ideographic Space (should be blocked)
echo "[TEST 4] Ideographic Space (U+3000) - Control\n";
echo "----------------------------------------\n";
$test4 = "admin" . "\xE3\x80\x80" . "test";
echo "Payload hex: " . bin2hex($test4) . "\n";
echo "Expected:    61646d696ee3808074657374\n";
echo "Match: " . (bin2hex($test4) === "61646d696ee3808074657374" ? "YES ✓" : "NO ✗") . "\n";
$blocked4 = preg_match('#[\x00-\x1F\x7F\s]#u', $test4);
echo "Status: " . ($blocked4 ? "BLOCKED ✓" : "VULNERABLE ✗") . "\n\n";

// Summary
echo "========================================\n";
echo "SUMMARY\n";
echo "========================================\n";
$vuln_count = (!$blocked1 ? 1 : 0) + (!$blocked2 ? 1 : 0) + (!$blocked3 ? 1 : 0);
echo "Vulnerabilities found: $vuln_count/3\n";
echo "Patch effectiveness: " . ((3 - $vuln_count) / 3 * 100) . "%\n\n";

if ($vuln_count > 0) {
 echo "  CRITICAL: Patch is INSUFFICIENT!\n";
 echo "Working exploits:\n";
 if (!$blocked1) echo "  • Zero-Width Space\n";
 if (!$blocked2) echo "  • RTL Override\n";
 if (!$blocked3) echo "  • Cyrillic Homograph\n";
} else {
 echo "✓ All exploits blocked\n";
}
echo "\n========================================\n";
?>

推荐的修复方案 将当前的黑名单模式替换为白名单方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 当前(易受攻击):
if (preg_match('#[\x00-\x1F\x7F\s]#u', $this->request['login'])) {
 $this->aErrors = [$GLOBALS['strInvalidUsername']];
}

// 推荐(安全):
if (empty($this->request['login']) ||
 strlen($this->request['login']) > 255 ||
 !preg_match('/^[a-zA-Z0-9._@-]+$/D', $this->request['login']) ||
 $this->request['login'] !== trim($this->request['login'])) {
 $this->aErrors = [$GLOBALS['strInvalidUsername']];
}

其他建议:

  • 验证前进行Unicode规范化 (NFC)
  • 实施易混淆字符检测
  • 记录可疑的用户名模式
  • 在管理UI中显示十六进制表示形式

开发人员的验证步骤

  1. 将上述测试脚本保存为 test_vulnerability.php
  2. 运行:php test_vulnerability.php
  3. 观察输出 - 您应该会看到3个 VULNERABLE 结果
  4. 应用推荐的修复
  5. 再次运行测试 - 所有结果都应显示为 BLOCKED

POC: [此处为截图图片引用,内容略]

参考资料

影响

  • 最危险 - 视觉上无法与"admin"区分
  • 完美的钓鱼攻击载体
  • 用户无法检测到伪造账户
  • 凭据收集
  • 管理员冒充攻击

附件 [此处为附件图片引用,内容略]

时间线及讨论摘要

  • 2025年11月19日,安全研究员 kassem_s94 提交了详细的漏洞报告。
  • 项目方 (mbeccati) 承认报告极其详细,并提到最初考虑过应用字符白名单,但认为支持Unicode用户名是一个不错的功能,现在意识到这不是一个好主意。
  • 经过讨论,项目方提供了一个补丁 (h1-3434156.patch)。
  • 研究员验证了新补丁成功阻止了所有利用方法。
  • 项目方决定此报告值得一个新的CVE-ID (CVE-2025-55129),因为之前的CVE (CVE-2025-55127) 仅专注于空格问题。
  • 报告于2025年11月26日公开披露。

报告详情

  • 报告于:2025年11月19日,UTC 21:07
  • 报告者:kassem_s94
  • 报告对象:Revive Adserver
  • 报告ID:#3434156
  • 状态:已解决
  • 严重性:中 (5.4)
  • 披露于:2025年11月26日,UTC 14:18
  • 弱点:不恰当的身份验证 - 通用
  • CVE ID:CVE-2025-55129
  • 赏金:隐藏
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计