存储过程权限绕过本质是执行上下文与权限继承机制差异所致:sql server动态sql默认调用者上下文、pg需显式设security invoker、mysql默认definer且不校验内部语句、oracle包体权限绑定定义者账号。

SQL Server 存储过程执行权限被绕过怎么办
直接给结论:EXECUTE 权限本身不隔离数据访问,只要存储过程中用了动态 SQL 或 EXECUTE AS,调用者就可能间接读写本不该碰的表。这不是配置漏了,是权限模型本身的“信任传递”特性。
常见错误现象:The SELECT permission was denied on the object 'Orders', database 'Sales', schema 'dbo' 没报,但用户却查出了 Orders 表数据;或者 DBA 明明只给了 EXECUTE 权限,用户却能通过 sp_executesql 插入敏感表。
- 检查存储过程是否含
EXEC(@sql)、sp_executesql—— 这类代码默认以调用者上下文运行,权限继承自调用者,不是定义者 - 若用了
EXECUTE AS OWNER或EXECUTE AS 'sa',等于把高权限借出去了,Owner 有啥权限,调用者就能干啥 - 避免在存储过程中硬编码表名后拼接进动态 SQL,否则权限校验完全失效;改用
sys.dm_exec_describe_first_result_set做元数据预检(仅限 SQL Server 2012+)
PostgreSQL 中如何让函数只拥有最小表访问权
PostgreSQL 的函数默认以定义者(SECURITY DEFINER)或调用者(SECURITY INVOKER)身份运行 —— 这个开关决定权限边界在哪,90% 的越权都源于设错了它。
使用场景:一个 Web 应用账号 app_user 需要调用 get_user_profile(),但不能直接查 users 表。
- 必须显式声明
SECURITY INVOKER,否则函数自动按SECURITY DEFINER执行,权限等同于函数创建者(通常是postgres) - 给
app_user只授EXECUTE权限:GRANT EXECUTE ON FUNCTION get_user_profile() TO app_user; - 函数体里所有表操作(
SELECT/UPDATE)都会校验app_user是否有对应权限 —— 所以你得单独GRANT SELECT ON users TO app_user,哪怕只是函数内部用 - 别依赖
search_path隐式解析表名;明确写public.users,防止因 schema 权限混乱导致意外访问
MySQL 存储过程权限校验为何总失效
MySQL 的存储过程权限控制最易误判:它不校验过程内语句的对象权限,只校验「能否执行该过程」—— 换句话说,EXECUTE 权限开闸后,过程里写个 DROP TABLE mysql.user 都不会被拦(当然实际会因 SUPER 权限缺失失败,但错误类型不是权限拒绝)。
典型表现:用户没有 SELECT 权限,却能通过存储过程查出任意表;DBA 查 SHOW GRANTS FOR 'u'@'%' 看着很干净,但风险已在过程体里埋好。
- MySQL 8.0+ 开始支持
SQL SECURITY DEFINER/INVOKER,但默认是DEFINER,且DEFINER用户必须存在且有对应对象权限 —— 别用不存在的用户如'admin'@'localhost'当定义者 - 过程里所有 DML 必须由
DEFINER用户具备权限,而不是调用者;所以定义者账号本身就得是权限最小化账号,而非 root - 禁用
log_bin_trust_function_creators=ON(尤其主从环境),否则函数创建不校验DETERMINISTIC等属性,可能引发复制中断
Oracle PL/SQL 包权限收敛的关键动作
Oracle 包(Package)的权限粒度比单个过程更难控:包头(Spec)可授 EXECUTE,但包体(Body)里的私有过程、游标、变量全无独立权限控制 —— 一旦包头可执行,整个包体逻辑就在调用者上下文中跑。
最容易被忽略的一点:包里用 AUTHID CURRENT_USER 不等于安全,它只是把权限检查推迟到运行时;如果包体里有 EXECUTE IMMEDIATE,照样会按调用者权限执行动态语句。
- 所有包必须显式声明
AUTHID DEFINER(默认值),并确保定义者账号(DEFINER)只拥有该包严格需要的表权限,且该账号禁止交互式登录 - 避免在包中构造用户输入拼接的 SQL 字符串;改用绑定变量 +
DBMS_SQL预编译,至少能阻断大部分注入路径 - 用
SELECT * FROM USER_TAB_PRIVS WHERE TABLE_NAME = 'T'定期审计定义者账号实际持有的权限,别信初始化脚本里写的“已清理”
真正麻烦的从来不是“怎么加权限”,而是“怎么证明没多给”。每个数据库对存储过程权限的解释逻辑不同,同一句 GRANT EXECUTE 在 SQL Server、PG、MySQL、Oracle 下触发的校验链路完全不同 —— 别依赖直觉,得看执行计划里的实际执行上下文。










