动态sql默认以调用者身份执行,不继承定义者权限;sql server中sp_executesql和exec(@sql)均遵循此规则,需显式使用execute as切换上下文。

动态SQL默认走调用者上下文,不是定义者
SQL Server 中 sp_executesql 和 EXEC(@sql) 默认以**调用者(caller)身份**执行,不继承存储过程定义者的权限。哪怕你用高权限账号创建了过程、设了 EXECUTE AS OWNER,只要没显式切换上下文,动态 SQL 一跑,权限就回落到当前连接用户身上。
常见错误现象:SELECT permission was denied on object 'Orders' 没报,但用户却通过 sp_executesql 查出了 Orders 表数据;或者 DBA 只给了 EXECUTE 权限,用户却能插入敏感表。
- 检查过程体里是否含
sp_executesql、EXEC(@sql)、EXECUTE IMMEDIATE(Oracle)等动态执行语句 - SQL Server:用
EXECUTE AS 'sa'或EXECUTE AS CALLER显式声明上下文,别依赖默认行为 - PostgreSQL:函数必须显式写
SECURITY INVOKER才会校验调用者权限;不写则默认SECURITY DEFINER,但动态 SQL 仍可能绕过——因为权限校验发生在解析阶段,而动态 SQL 在运行时才解析 - MySQL:根本不管调用者权限,只看
DEFINER是否有对应表权限;但如果用了CONCAT拼表名,校验直接跳过,等于裸奔
拼接对象名导致权限校验完全失效
一旦你把表名、列名、schema 名拼进字符串再执行,数据库就无法在解析阶段做权限检查——它只认静态 SQL 的对象引用。比如 SET @sql = 'SELECT * FROM ' + @table_name,哪怕 @table_name 是用户输入,后续也没法拦。
使用场景:多租户分表、按条件切库、报表字段动态排序。
- SQL Server 必须用
QUOTENAME(@schema)+ '.' +QUOTENAME(@table),不能先拼串再套QUOTENAME() - 禁止用
REPLACE(@input, '''', '''''')或正则过滤来“消毒”表名——这些全可被绕过 - MySQL 和 PostgreSQL 不提供
QUOTENAME()等价物,必须靠白名单硬控:IF @table NOT IN ('orders_2024', 'orders_2025') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid table'; - Oracle 的
EXECUTE IMMEDIATE同样不校验动态对象名权限,得靠DBMS_ASSERT包做校验,例如DBMS_ASSERT.SQL_OBJECT_NAME(@table)
不同数据库对动态SQL的权限模型差异极大
不是所有数据库都把动态 SQL 当成“普通语句”处理。权限是否生效、何时生效、校验谁的权限,每个系统逻辑完全不同。
- SQL Server:动态 SQL 默认 caller 上下文;
EXECUTE AS可临时提升,但作用域仅限当前批处理 - PostgreSQL:函数设
SECURITY DEFINER后,动态 SQL 仍以定义者身份运行,但若内部用current_user做逻辑分支,就可能暴露定义者账号信息 - MySQL:不校验过程内任何语句的对象权限,只认
DEFINER是否存在且有对应权限;动态拼接后连DEFINER权限都不查,彻底跳过权限系统 - Oracle:默认 DR(Definer’s Rights)模式,但动态 SQL(
EXECUTE IMMEDIATE)权限校验发生在运行时,且只检查当前会话有效角色——而角色在存储过程中默认不激活,除非显式加AUTHID CURRENT_USER
为什么加了EXECUTE权限还是被拒?
给用户授了 EXECUTE ON PROCEDURE,不代表他能跑通过程里的动态 SQL。这个权限只管“能不能调”,不管“调了之后能不能活”。真正卡住的地方往往在第二层甚至第三层。
- SQL Server:调用者没被授予目标表的
SELECT权限,sp_executesql就崩;即使加了EXECUTE AS,若目标表在另一个数据库,还得开跨库信任或显式授权 - PostgreSQL:函数设了
SECURITY INVOKER,但调用者没被GRANT SELECT ON public.orders TO app_user,第一步 SELECT 就失败 - MySQL:
DEFINER用户对过程里拼出的表名没有权限,或该用户已被删,此时CALL直接报错ERROR 1449 (HY000): The user specified as a definer does not exist - Oracle:即使
DEFINER有CREATE TABLE权限,若没显式GRANT CREATE TABLE TO PUBLIC或开启角色,EXECUTE IMMEDIATE 'CREATE TABLE'仍报ORA-01031
真正容易被忽略的是:动态 SQL 的权限边界,从来不由存储过程本身的 GRANT 决定,而由它运行时那一刻的执行上下文、对象名解析方式、以及底层数据库的权限校验时机共同决定。改一个 QUOTENAME() 调用位置,或漏掉一行 GRANT SELECT ON other_db.t TO app_user,就可能让整条链路失效。










