必须显式声明AUTHID CURRENT_USER,否则EXECUTE IMMEDIATE绕过调用者权限校验;再配合白名单校验表名列名及精确授予权限,才能实现最小权限控制。

不能靠存储过程内部查 USER 或 SUSER_NAME() 来判断调用者权限——数据库不提供“当前用户对某张表有没有 SELECT 权限”这种实时校验函数,必须靠执行上下文 + 显式授权 + 白名单三者配合。
SQL Server 中如何让 EXECUTE 权限真正隔离数据访问
很多人以为只给 EXECUTE 权限就能挡住越权,结果发现用户能通过存储过程查出没被授权的表。这不是配置漏了,是默认行为:SQL Server 存储过程在未声明 EXECUTE AS 时,以 CALLER 上下文运行,动态 SQL(如 sp_executesql)也继承调用者权限。
- 必须显式加
WITH EXECUTE AS 'readonly_user',且该账户只被授予极少数表的SELECT权限(不能是db_owner或sysadmin) - 避免用
EXECUTE AS OWNER或EXECUTE AS SELF——前者把创建者全部权限借出去,后者绑定死创建者身份,维护时容易失控 - 所有权链(ownership chaining)只在所有对象同属一个 schema 时生效;一旦跨 schema(比如过程在
dbo、表在sales),权限检查立刻恢复为调用者视角 - 如果过程里用了
INSERT/UPDATE/DELETE,哪怕加了EXECUTE AS,也要确保目标表上已对readonly_user单独授过对应 DML 权限,否则报错不是权限拒绝,而是对象找不到
Oracle 存储过程中动态 SQL 的权限校验为何总失效
Oracle 默认所有存储过程都是定义者权限(DEFINER RIGHTS),EXECUTE IMMEDIATE 完全绕过调用者权限检查——哪怕你只给了 EXECUTE 权限,过程里写个 DELETE FROM employees,只要创建者有这权限,就真能删。
- 第一步必须加
AUTHID CURRENT_USER,否则后面所有防御都白搭;加了之后,EXECUTE IMMEDIATE才会按调用者身份做权限校验 - 第二步禁止拼接表名/列名:
v_table := user_input; EXECUTE IMMEDIATE 'SELECT * FROM ' || v_table;这种写法等于废掉AUTHID CURRENT_USER—— 数据库只校验最终语句是否合法,不关心v_table是哪来的 - 正确做法是用
DBMS_ASSERT.SIMPLE_SQL_NAME(v_table)校验表名,或从预定义白名单中匹配,列名同理,要用DBMS_ASSERT.ENQUOTE_NAME包裹后再拼 - 绑定变量(
USING)只能防 SQL 注入,对越权无效;权限检查发生在解析阶段,而绑定值在执行时才代入
PostgreSQL 函数里怎么确认调用者真有表权限
PostgreSQL 的函数默认是 SECURITY DEFINER,也就是以函数创建者身份运行,权限等同于 postgres 用户——这是越权高发点。必须显式声明 SECURITY INVOKER,否则 GRANT 给调用者的任何权限都不起作用。
- 创建函数时必须写明:
CREATE FUNCTION get_user() RETURNS TABLE(...) SECURITY INVOKER AS ... - 然后单独执行:
GRANT SELECT ON users TO app_user;—— 注意,不是给函数授,是给表授;函数体里每条SELECT都会触发对app_user的权限检查 - 别依赖
search_path隐式找表;写死 schema 名,比如SELECT * FROM public.users,防止因路径污染访问到其他 schema 下同名敏感表 - 如果函数要查多个表,每个表都得单独对调用者授对应权限;缺一个,执行时就报
permission denied for table xxx
MySQL 存储过程根本没法靠权限机制防越权
MySQL 5.7 及更早版本,存储过程权限控制基本不可靠:它只校验「能不能执行这个过程」,不校验过程体内语句的对象权限。哪怕用户没被授过 SELECT ON salaries,只要过程里写了 SELECT * FROM salaries,且过程创建者有这权限,就能查出来。
- MySQL 8.0+ 引入了
SQL SECURITY DEFINER/INVOKER,但INVOKER模式下仍不校验过程内语句权限(只校验函数/过程本身执行权),和 PostgreSQL 不同 - 真正可行的方案只有两个:一是用视图 + 行级安全策略(
CREATE POLICY),但仅对SELECT有效,且不作用于过程内部;二是彻底放弃用过程做权限边界,改由应用层代理查询,过程只做纯计算逻辑 - 如果非要用过程,至少把敏感字段过滤掉,比如写死
SELECT id, name, status FROM users WHERE status = 'active',而不是SELECT * - 别信 “我只给了 EXECUTE 权限,他就不能干别的” —— 在 MySQL 里,这句话基本等于没设防
最常被忽略的一点:权限模型不是开关,是上下文叠加。SQL Server 的 EXECUTE AS、Oracle 的 AUTHID CURRENT_USER、PostgreSQL 的 SECURITY INVOKER,这三个声明必须出现在函数/过程定义里,且不能被注释掉、不能靠运行时 ALTER 修改——它们决定的是整个执行流的起点权限上下文,一旦设错,后面所有白名单、绑定变量、错误捕获都只是补丁,不是防线。

















