oracle存储过程默认使用定义者权限(definer rights),execute immediate绕过调用者权限校验;需显式声明authid current_user,并配合白名单校验与精确授予权限,才能实现最小权限控制。
oracle存储过程里一用execute immediate就绕过权限校验,不是配置漏了,是默认走定义者权限(definer rights),调用者直接继承过程创建者的全部权限。哪怕你只给了execute权限,过程里执行insert into users照样能写,只要创建者有这权限。
为什么加了AUTHID CURRENT_USER才真正生效
Oracle默认所有存储过程都是定义者权限,过程内部所有SQL都以创建者身份运行,跟调用者实际权限无关。加AUTHID CURRENT_USER后,过程内每条语句(包括EXECUTE IMMEDIATE)都会按调用者身份做权限检查——这才是最小权限落地的前提。
- 不加这句:过程创建者是
ADMIN,ADMIN能删employees表,调用者哪怕只有SELECT权限,也能通过过程删表 - 加了这句:调用者必须自己有
DELETE ON employees,否则EXECUTE IMMEDIATE 'DELETE FROM employees'直接报ORA-01031: insufficient privileges - 注意:
AUTHID CURRENT_USER只影响过程体内SQL的权限上下文,不影响过程本身的EXECUTE权限授予逻辑
EXECUTE IMMEDIATE里拼表名/列名等于废掉权限控制
即使开了AUTHID CURRENT_USER,只要动态SQL里硬拼了不可信输入,权限校验就形同虚设。数据库只校验最终拼出的语句是否合法,不关心这个表名是不是用户传进来的。
- 错例:
v_table := user_input; EXECUTE IMMEDIATE 'SELECT * FROM ' || v_table;——user_input填salaries,调用者没查salaries权限也照查 - 正确做法:表名必须来自白名单或
DBMS_ASSERT.SIMPLE_SQL_NAME校验,比如DBMS_ASSERT.SIMPLE_SQL_NAME(v_table)会拒绝含点号、空格、引号的非法输入 - 列名同理,不能拼,要用预定义字段列表或
DBMS_ASSERT.ENQUOTE_NAME包裹后再拼
绑定变量只能防注入,防不住越权
用USING传参确实能拦住' OR 1=1 --这类注入,但对越权毫无作用。权限校验发生在SQL解析阶段,绑定变量值在执行时才代入,此时表和列已经确定了。
- 安全写法:
EXECUTE IMMEDIATE 'SELECT name, email FROM users WHERE id = :id' USING input_id;—— 表名users固定,调用者必须有SELECT ON users - 危险写法:
EXECUTE IMMEDIATE 'SELECT ' || input_columns || ' FROM ' || input_table USING input_id;—— 即使input_columns用了绑定变量,input_table一拼就失控 - 真正要防越权,得靠
AUTHID CURRENT_USER+ 白名单校验 + 调用者显式授表级权限,三者缺一不可
最常被忽略的一点:AUTHID CURRENT_USER开启后,调用者权限必须手动授予——DBA不会自动把过程里涉及的每张表权限都配好。你得明确知道过程里查了哪些表、改了哪些字段,再一条条GRANT SELECT/UPDATE ON schema.table TO app_user。漏一条,过程就跑不起来;多给一条,越权风险就回来。











