dinosn/CVE-2026-32710
GitHub: dinosn/CVE-2026-32710
针对 MariaDB 11.4.x JSON_SCHEMA_VALID() 堆溢出漏洞的完整利用链 PoC,演示从仅 SELECT 权限到持久化全库提权及 UDF 远程命令执行的全过程。
Stars: 12 | Forks: 0
# CVE-2026-32710
**MariaDB `JSON_SCHEMA_VALID()` 中的堆缓冲区溢出 → 持久化权限提升 → UDF RCE**
| | |
|---|---|
| **受影响版本** | MariaDB 11.4.x(已在 11.4.9 上确认) |
| **缺陷** | `json_get_normalized_string()` 中的越界写入 — `strncpy` 在没有边界检查的情况下写入 128 字节的 `DYNAMIC_STRING` |
| **影响** | 仅 `SELECT` 权限的用户 → `ALL PRIVILEGES WITH GRANT OPTION` → 通过 UDF 执行任意命令 |
| **来源** | `sql/json_schema_helper.cc:91` |
## 攻击前
`lowpriv` 只能对 `test` 数据库执行 `SELECT` 操作。系统表访问被拒绝。

## 漏洞利用
单个 Python 脚本执行堆布局准备、通过用户变量元数据实现两步任意写入、通过 `GRANT ALL` 持久化权限提升,并通过 UDF 实现代码执行:
```
python3 exploit.py
```

## 攻击后
`lowpriv` 现在拥有 `ALL PRIVILEGES WITH GRANT OPTION`,可以读取系统表、读写任意文件,并作为 `mysql` 用户执行 OS 命令。
授权在服务器重启后依然保留。

## 阶段
### 阶段 1:权限提升
```
┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192-byte │ │ Write to @c │ │ Write to @a │
│ overflow │ │ through the │ │ through the │
│ corrupts │ │ corrupted │ │ redirected │
│ entry_c's │ │ pointer: │ │ pointer: │
│ value ptr │ │ │ │ │
│ (2-byte │ │ entry_a → │ │ master_access│
│ partial │ │ .value = │ │ = 0xFFFF.. │
│ overwrite)│ │ &master_ │ │ (ALL PRIVS) │
│ │ │ access │ │ │
└───────────┘ │ .length= 9 │ └──────────────┘
└──────────────┘
```
1. **堆布局准备 (Heap groom)** — 超过 100 个用户变量耗尽了 tcache,迫使 `Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c` 被连续分配。
2. **溢出 (Overflow)** — `JSON_SCHEMA_VALID` 触发了一次 192 字节的 `strncpy` 写入越界,越过了 128 字节的缓冲区,破坏了 `entry_c→value`(在同一 64 KB 页面内的 2 字节部分指针覆盖),使其指向 `entry_a + 32`。
3. **跳转 1 (Hop 1)** — 对 `@c` 进行赋值,通过被破坏的指针将 126 字节写入 `entry_a` 的元数据中,设置如下:
- `entry_a→value = &Security_context::master_access`
- `entry_a→length = 9`
4. **跳转 2 (Hop 2)** — 对 `@a` 进行赋值,通过重定向的 `entry_a→value` 指针写入 8 字节的 `0xFF` → `master_access = ALL PRIVILEGES`。
5. **持久化 (Persist)** — 该会话现在持有所有权限。`GRANT ALL` 将权限提升提交到由 Aria 支持的 `mysql.global_priv` 表中。会话最终在清理期间崩溃(由于残留的堆损坏),但 GRANT 已完成检查点保存并在重启后保留。
### 阶段 2:UDF RCE
持久化之后,服务器会重启(崩溃恢复),并利用提升的权限来安装 UDF 共享库:
1. `LOAD_FILE('/tmp/raptor_udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so'`
2. `CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'raptor.so'`
3. `SELECT sys_exec('id > /tmp/pwned')`
### 解决的关键约束
| 约束 | 解决方案 |
|---|---|
| `STRING_RESULT` 在重新分配检查前执行 `length++` | Payload 为 N−1 字节,因此 N−1+1 = N 匹配存储的长度 → 在被破坏的指针上不触发重新分配 |
| 126 字节的 hop2 破坏了 `Security_context` 之后的 THD 字段 | 在 hop1 中设置 `entry_a→length = 9`,使 hop2 只写入 8 字节 (master_access) + 1 个 NUL 字符 |
| `Security_context` 中的 `master_access` 偏移量 | 距结构体基地址 1712 字节(`priv_user[384]` + `proxy_user[645]` + `priv_host[256]` + `priv_role[384]` + 填充 + 指针) |
| Aria 崩溃恢复会回滚未提交的写入 | `GRANT ALL` + 10 秒的 `SLEEP` 允许在会话清理崩溃之前完成 Aria 检查点保存 |
| 堆布局在不同连接之间会有所不同(即使 ASLR=0) | 内联的每次尝试扫描 `/proc/1/mem` 会发现每个连接的 entry_a 和 master_access |
| plugin_dir 为 root 所有 | Dockerfile 预设了 `chmod 777`(便于实验) |
## 实验环境搭建
```
# 1. 构建并启动容器
./setup.sh
# 2. 在 Docker 主机上禁用 ASLR
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. 运行 exploit(每次尝试自动校准)
python3 exploit.py
# 4. 自定义命令
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
```
### 前置要求
- Docker (x86_64)
- Python 3
- 在 Docker **宿主机**上禁用 ASLR(`/proc/sys/kernel/randomize_va_space = 0`)
- 容器以 `--cap-add SYS_PTRACE` 运行(用于访问 `/proc/1/mem`)
### 选项
```
--calibrate Measure heap layout constants and exit
--cmd CMD Command for Stage 2 UDF execution (default: id > /tmp/pwned)
--stage1-only Run privilege escalation only, skip UDF RCE
--attempts N Max stage 1 attempts (default: 5)
--host HOST MariaDB host (default: 127.0.0.1)
--port PORT MariaDB port (default: 3306)
```
### 实验辅助工具提供的功能
漏洞利用脚本以 root 身份进入容器读取 `/proc/1/mem`。
这用于两件事:
1. **堆布局发现** — 定位三个哨兵 `user_var_entry` 结构体,并验证它们是相邻的(entry_a+32 与 entry_c→value 在同一个 64KB 页面内,用于 2 字节的部分覆盖)。
2. **`Security_context` 地址** — 查找 `master_access` 字段,以作为两步写入的目标。
扫描在每次尝试中内联运行,因为由于 MariaDB 的线程池分配不同的内存分配区域,堆布局在不同连接之间会有所不同(即使 ASLR=0)。实际的漏洞利用链 —— 溢出 + hop1 + hop2 —— 是通过 TCP 连接执行的纯 SQL。
在真实场景中,攻击者需要单独的信息泄露漏洞(或侧信道)来获取这些地址。
## 文件
| 文件 | 描述 |
|---|---|
| `exploit.py` | 两阶段漏洞利用:权限提升 (TCP SQL) + UDF RCE |
| `raptor_udf.c` | UDF 源码 — `sys_exec()` 调用 `system()` |
| `Dockerfile` | 实验环境容器镜像(编译 UDF,开放 plugin_dir) |
| `init.sql` | 创建 `lowpriv` 用户 |
| `setup.sh` | 构建并启动实验环境 |
| `screenshots/` | 终端截图 |
完整漏洞利用输出
标签:0day漏洞, C++, CISA项目, CVE-2026-32710, DYNAMIC_STRING, JSON_SCHEMA_VALID, MariaDB, Python利用脚本, strncpy, UDF RCE, WITH GRANT OPTION, 二进制漏洞, 任意代码执行, 协议分析, 堆喷射, 堆溢出, 多线程, 提权, 数据擦除, 权限提升, 网络安全, 请求拦截, 越界写, 逆向工具, 隐私保护