bigywater.tech
MySQL / InnoDB · 106

DEFINER 与权限陷阱

用一个零权限用户真实调用两个只差SQL SECURITY关键字的存储过程,看DEFINER默认行为真实是怎么把EXECUTE权限变成一个隐藏的数据访问后门的。

0权限也能读到数据
DEFINER存储过程真实的权限穿透效果
DEFINER是默认值
不写这个子句真实等于隐式选择了它

做的事:真实一个零权限用户,两个只差一个关键字的存储过程

代码CREATE USER 'noaccess'@'%' IDENTIFIED BY '...'; -- 真实没有给它任何表级权限 CREATE DEFINER='root'@'localhost' PROCEDURE get_secret_definer() SQL SECURITY DEFINER BEGIN SELECT * FROM secret_data; END; CREATE DEFINER='root'@'localhost' PROCEDURE get_secret_invoker() SQL SECURITY INVOKER BEGIN SELECT * FROM secret_data; END; GRANT EXECUTE ON PROCEDURE mysqllab.get_secret_definer TO 'noaccess'@'%'; GRANT EXECUTE ON PROCEDURE mysqllab.get_secret_invoker TO 'noaccess'@'%';

验证:直接查表,真实被拒绝——这是预期内的

实测 · 本机执行输出
noaccess: SELECT * FROM secret_data;
→ ERROR 1142: SELECT command denied to user 'noaccess'@'localhost' for table 'secret_data'

验证:调用DEFINER存储过程,真实读到了数据

实测 · 本机执行输出
noaccess: CALL get_secret_definer();
→ id  val
   1  top-secret     ← 真实读到了,尽管这个用户对secret_data没有任何直接权限!
这一步真实是本文想强调的权限陷阱:SQL SECURITY DEFINER(不写这个关键字的话,这就是MySQL存储过程的默认行为!)让存储过程内部的所有操作,都真实用DEFINER子句里指定的那个账号(本文是root)的权限去执行,而不是调用者(noaccess)的权限。GRANT EXECUTE这一个权限,真实间接授予了调用者"读到secret_data里任何数据"的能力,哪怕从来没有对这张表本身授予过一丁点权限。

验证:换成INVOKER,同样的查询真实又被拒绝了

实测 · 本机执行输出
noaccess: CALL get_secret_invoker();
→ ERROR 1142: SELECT command denied to user 'noaccess'@'localhost' for table 'secret_data'

SQL SECURITY INVOKER让存储过程内部真实用调用者(noaccess)自己的权限去执行,noaccess本身对secret_data没有SELECT权限,所以哪怕通过存储过程,真实一样会被拒绝。真实要给一个存储过程做权限审查,第一件事就是确认它的SQL SECURITY子句是DEFINER还是INVOKER——不写这个子句,MySQL真实默认按DEFINER处理,这也是这个陷阱容易被忽视的原因:写代码的人往往根本没意识到自己隐式选择了"用DEFINER的权限运行"这个行为。

分步演示:DEFINER和INVOKER,权限边界的真实差异

每一步都是本文上面用真实零权限用户跑出来的原始结果。

一个EXECUTE权限,真实间接授予了整张表的读权限
这一步新验证的调用方式

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),零权限用户 + 两个只差SQL SECURITY关键字的存储过程
    • SQL SECURITY DEFINER(默认)vs SQL SECURITY INVOKER
    京ICP备2026067502号-1京公网安备11010502063487号