EXECUTE IMMEDIATE 本身安全,危险源于拼接用户输入;必须用 USING 绑定变量(非表达式),IN 子句用 MEMBER OF 或固定占位符,结构信息(表名/排序字段)须经 DBMS_ASSERT 校验或 CASE 预置,且需 AUTHID CURRENT_USER 配合白名单与显式授权。

EXECUTE IMMEDIATE 本身不危险,危险的是把用户输入拼进字符串里再执行——只要出现 || user_input,就等于主动交出数据库控制权。
USING 必须用,且只能传变量,不能传表达式
绑定变量的安全性来自 Oracle 在执行前将值与 SQL 结构完全分离。一旦你写成 USING UPPER(name),PL/SQL 会直接报错:ORA-06550,因为 USING 只接受已声明、已赋值的 PL/SQL 变量(如 VARCHAR2、NUMBER),不支持运行时计算。
- 错误写法:
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = :n' USING UPPER(user_name); - 正确写法:
l_name := UPPER(user_name); EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = :n' USING l_name; - 多个参数必须严格按占位符顺序匹配:
:1对应USING第一个值,:2对应第二个,以此类推;命名绑定(如:name)只是可读性增强,底层仍按位置绑定 - 占位符前后不能有空格或换行,否则 Oracle 不识别为绑定点,例如
'WHERE id = :1 '(末尾空格)会导致 ORA-06550
IN 子句不能直接绑定列表,得用 MEMBER OF 或预设占位符
Oracle 不支持 WHERE id IN :id_list 这种语法,也不允许 USING 传入数组类型变量。试图这么写会触发 ORA-01008:not all variables bound,或者更隐蔽地拼字符串回退到高危区。
- 推荐方案:
WHERE id MEMBER OF :id_list,绑定时传sys.odcinumberlist(101, 102, 103)—— 这是 Oracle 原生支持的集合类型,安全且高效 - 简单场景可用固定上限:
WHERE id IN (:1, :2, :3, :4, :5) AND id IS NOT NULL,未用位置传NULL,靠AND id IS NOT NULL过滤无效项 - 绝对禁止:
LISTAGG拼字符串 +INSTR模糊匹配,这等于绕开绑定机制,重新打开注入大门
表名、列名、排序字段等结构信息不能绑,但裸拼就是漏洞
绑定变量只适用于“值”,不适用于 SQL 语法结构。Oracle 明确拒绝 ORDER BY :sort_col 这类写法(ORA-01027),但很多人误以为“只要没报错就能拼”,结果把 user_table 直接塞进字符串,等于给攻击者留了 DROP TABLE 的后门。
- 必须校验后再拼:
safe_table := DBMS_ASSERT.SIMPLE_SQL_NAME(p_table_name); EXECUTE IMMEDIATE 'SELECT * FROM ' || safe_table; -
DBMS_ASSERT.SIMPLE_SQL_NAME会拦截空格、分号、双引号、点号、Unicode 变体等非法字符,非法输入直接抛异常,不返回处理后的字符串 - 排序字段等动态逻辑,要用
CASE WHEN预置:ORDER BY CASE :sort_by WHEN 'name' THEN name WHEN 'id' THEN id END,:sort_by本身仍需白名单校验 - 别用
DBMS_ASSERT.ENQUOTE_NAME当校验函数——它只加双引号,不验证合法性;也别信正则^[a-zA-Z0-9_]+$,它防不住宽字节截断
AUTHID CURRENT_USER 是权限控制的前提,但不是注入防护
即使你把所有值都用 USING 绑定了,只要动态 SQL 里拼了不可信的表名,调用者依然可能越权访问敏感表。而默认的定义者权限(DEFINER RIGHTS)会让整个过程以创建者身份运行,彻底绕过调用者权限检查。
- 必须显式声明:
CREATE OR REPLACE PROCEDURE my_proc AUTHID CURRENT_USER AS ... - 但仅加这句不够:调用者必须实际拥有目标表的
SELECT权限,否则EXECUTE IMMEDIATE 'SELECT * FROM employees'会直接报 ORA-01031 - 白名单校验和
AUTHID CURRENT_USER必须同时存在——前者防注入,后者防越权;缺一不可 - 最容易被忽略的是:DBA 不会自动授予过程内涉及的所有表权限,你得手动
GRANT SELECT ON hr.employees TO app_user;,漏一条就失败,多给一条就风险
真正难的不是写对 USING,而是把所有动态拼接点都识别出来——哪怕只是日志语句里的 v_msg、导出文件名里的 p_format,只要进了 EXECUTE IMMEDIATE 字符串,就必须走校验或绑定。没有“差不多安全”的中间态。


















