SQL 动态 SQL 的 EXECUTE IMMEDIATE 与 SQL 注入防护规范

2次阅读

execute immediate 本身不危险,根本原因是将未过滤的用户输入直接拼入sql字符串;绑定变量仅适用于值,不适用于表名、列名等语法结构,后者必须通过白名单校验。

SQL 动态 SQL 的 EXECUTE IMMEDIATE 与 SQL 注入防护规范

EXECUTE IMMEDIATE 为什么会触发 SQL 注入

根本原因不是 EXECUTE IMMEDIATE 本身危险,而是你把用户输入直接拼进字符串里再执行。oracle 不会帮你校验拼出来的 SQL 是否“合法”或“安全”,它只负责执行——哪怕那条 SQL 是 'DROP table users' || :user_input

常见错误现象:ORA-00911: invalid character 看似是语法错,实际常因未过滤掉分号、注释符(--/*)导致语句被截断或注入;更隐蔽的是布尔盲注式绕过,比如传入 ' OR 1=1 -- 让条件恒真。

  • 所有来自 :bind_variable 以外的拼接内容(尤其是 || 连接的字符串)都必须视为不可信输入
  • 动态表名、列名、排序字段等无法用绑定变量的地方,必须走白名单校验,不能靠正则“大概过滤”
  • 不要试图用 REPLACE 去删分号或单引号——攻击者可用 Unicode 变体、注释嵌套等方式绕过

哪些地方必须用绑定变量,哪些地方不能用

绑定变量(:var)只能替代 SQL 中的**值(value)**,不能替代对象名、关键字、函数名或语法结构。这是 Oracle 的硬限制,不是风格建议。

能用绑定变量的场景:WHERE name = :user_nameINSERT INTO t VALUES (:a, :b)ORDER BY :sort_col(⚠️注意:这个 :sort_col 实际上是非法的!见下一条)

  • WHERE / HAVING 条件中的值、INSERT/UPDATE 的列值、函数参数(如 TO_DATE(:date_str, 'YYYY-MM-DD'))——全都可以且必须用绑定变量
  • 表名、列名、ORDER BY 字段、GROUP BY 表达式、union 子句结构——统统不能用绑定变量,Oracle 会报 ORA-01027: bind variables not allowed for data definition operations
  • 想动态 ORDER BY?得用 CASE + 白名单:例如 ORDER BY CASE :sort WHEN 'name' THEN name WHEN 'id' THEN id END

动态对象名(表名/列名)的安全校验怎么做

没有银弹,唯一可靠方式是预定义白名单 + 严格字符校验。别信“我只允许字母数字下划线”——Oracle 对象名支持双引号包裹的任意字符(包括空格、中文、连字符),而双引号本身正是注入入口。

正确做法是把允许的表名列名穷举为常量集合,再用 INCASE 匹配:

IF p_table_name NOT IN ('employees', 'departments', 'projects') THEN   RAISE_APPLICATION_ERROR(-20001, 'Invalid table name'); END IF;
  • 校验必须在拼接 SQL 字符串之前完成,不能放在 EXECUTE IMMEDIATE 之后
  • 避免用 DBMS_ASSERTSQL_OBJECT_NAME——它只检查是否为合法对象名,不校验权限或业务白名单,且对同义词、带 schema 的名字行为复杂
  • 如果必须支持用户自定义表名(如租户隔离场景),应从数据字典查 ALL_TABLES 并限定 OWNER,而不是信任输入

EXECUTE IMMEDIATE 的性能和调试陷阱

每次执行不同字符串的 EXECUTE IMMEDIATE 都会触发硬解析,和没绑定变量的普通 SQL 一样慢。更麻烦的是,出错时指向 EXECUTE IMMEDIATE 行号,而不是动态 SQL 内部哪一行——调试成本陡增。

  • 把完整 SQL 赋给变量再执行,方便打印调试:v_sql := 'select * FROM ' || v_table || ' WHERE id = :1'; DBMS_OUTPUT.PUT_LINE(v_sql); EXECUTE IMMEDIATE v_sql INTO v_result using p_id;
  • 避免在循环里反复拼接并执行相同结构的 SQL——提取成带绑定变量的静态 SQL,或用 BULK COLLECT + FORALL
  • 注意 EXECUTE IMMEDIATE 默认不继承当前事务上下文,但异常时仍会回滚到最近的保存点;若需精确控制,显式加 SAVEPOINT

最易被忽略的一点:动态 SQL 中的注释(--)、分号(;)、引号嵌套,哪怕语法合法,也可能在特定 Oracle 版本或 NLS 设置下表现异常——上线前务必用真实边界值(含特殊字符用户名、含单引号地址)跑集成测试。

text=ZqhQzanResources