存储过程能绕过普通权限限制是因为以定义者(DEFINER)身份执行而非调用者,即使只读账号执行由root定义的过程,内部语句仍具root权限;常见风险操作包括INTO OUTFILE、LOAD DATA INFILE、调用高危函数及PREPARE+EXECUTE动态执行。

存储过程为什么能绕过普通权限限制
存储过程本身不是漏洞,但它会以定义者(DEFINER)身份执行,而不是调用者身份。这意味着:哪怕一个只读账号 report_user 没有 FILE 或 SUPER 权限,只要它能执行某个由 root@localhost 定义的存储过程,该过程内部的语句就会以 root 权限运行。
常见错误配置包括:
- DBA 为方便调试,统一用
DEFINER=CURRENT_USER或直接写死DEFINER='root'@'localhost' - 应用部署脚本自动创建存储过程,未显式指定
SQL SECURITY DEFINER/SQL SECURITY INVOKER - 迁移旧库时保留了高权限
DEFINER,但没同步更新账号权限模型
哪些存储过程操作最危险
真正构成提权链路的,是那些能触发系统级行为的内部逻辑。攻击者不关心你写了多少业务逻辑,只盯着这几类语句:
-
SELECT ... INTO OUTFILE—— 配合secure_file_priv可写路径,能写入 WebShell 或 UDF 动态库 -
LOAD DATA INFILE—— 若未禁用local_infile,可能读取服务器任意文件(如/etc/passwd) - 调用已存在的高危函数,比如
sys_eval()(若 UDF 已加载)、extractvalue()(报错注入辅助) - 嵌套执行
PREPARE+EXECUTE,动态拼接并执行任意 SQL,绕过静态权限检查
如何判断一个存储过程是否构成风险
不能只看“有没有 CREATE PROCEDURE 权限”,关键要查它的执行上下文。以下命令可快速定位隐患:
SELECT name, definer, security_type, sql_mode FROM mysql.proc WHERE definer NOT LIKE '%@localhost' OR security_type = 'DEFINER';
重点关注:
-
definer是root或其他高权限账号,且host不是localhost(意味着远程可调用) -
security_type = 'DEFINER'(默认值),而调用者权限远低于定义者 -
sql_mode包含NO_ENGINE_SUBSTITUTION以外的宽松模式,可能掩盖语法错误导致意外执行
特别注意:MySQL 8.0+ 默认开启 require_row_format 等安全模式,但若手动关闭或兼容旧应用,SQL SECURITY INVOKER 的约束也会被弱化。
修复时最容易忽略的细节
改完 DEFINER 或加 SQL SECURITY INVOKER 不等于万事大吉。真实环境中,这些点常被跳过:
- 存储过程里用了
CONCAT()拼接表名或字段名 → 仍可能触发二次注入,即使调用者是低权限账号 - 过程内调用另一个存储过程,而那个过程的
DEFINER更高 → 权限继承链没断干净 - 应用代码里用
CALL proc_name()传参,但没做参数过滤 → 输入直接进EXECUTE,变成“带权限的注入” - 删除过程后,对应的
mysql.proc行残留,或权限缓存未刷新(需FLUSH PRIVILEGES)
最稳妥的做法,是把所有非必需的存储过程设为 SQL SECURITY INVOKER,再逐个审计其内部是否含系统级操作;若必须用 DEFINER,则确保定义者账号本身已被严格限制(例如剥离 FILE、SUPER 权限,仅保留必要 DML)。


















