用一个零权限用户真实调用两个只差SQL SECURITY关键字的存储过程,看DEFINER默认行为真实是怎么把EXECUTE权限变成一个隐藏的数据访问后门的。
代码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'
noaccess: CALL get_secret_definer(); → id val 1 top-secret ← 真实读到了,尽管这个用户对secret_data没有任何直接权限!
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的权限运行"这个行为。
每一步都是本文上面用真实零权限用户跑出来的原始结果。
SQL SECURITY DEFINER(默认)vs SQL SECURITY INVOKER