
DBMS_SQL.PARSE 直接拼接字符串就是高危操作
只要把用户可控输入(比如 table_name、where_clause)直接拼进 DBMS_SQL.PARSE 的 SQL 字符串里,就等于给攻击者开了 SYS 权限执行任意语句的后门。这种写法在老系统里很常见,但 Oracle 官方早在 CPUoct2010 就明确将 DBMS_CDC_PUBLISH.CREATE_CHANGE_SET 这类拼接逻辑列为可被利用的注入入口。
典型错误模式:
sql_stmt := 'SELECT * FROM ' || user_input_table || ' WHERE id = ' || user_input_id;
一旦 user_input_table 是 'employees WHERE 1=1; EXECUTE IMMEDIATE ''GRANT DBA TO hacker''',后果不可控。
- 永远不要用
||拼接表名、列名、WHERE 条件片段 - 即使加了
DBMS_ASSERT,也不能覆盖所有动态对象名场景(比如带 schema 的双引号标识符) -
DBMS_SQL.PARSE的language_flag参数若设为V7,会绕过部分语法校验,加剧风险
用 DBMS_ASSERT 验证对象名和 SQL 片段的前提是可信上下文
DBMS_ASSERT 不是万能胶水,它只在输入来源受限、且语义明确时才安全。比如验证一个“应该只是纯字母数字的表名”,可以用 DBMS_ASSERT.SIMPLE_SQL_NAME;但若业务要求支持带连字符的租户前缀(如 tenant-abc.orders),这个函数会直接抛出异常,强行使用反而导致功能断裂。
更危险的是误用 DBMS_ASSERT.ENQUOTE_NAME:它只加双引号,不转义内容——输入 "employees"; DROP TABLE users -- 仍会被原样解析。
-
DBMS_ASSERT.SQL_OBJECT_NAME只校验是否为合法对象名,不防注入逻辑 - 对 WHERE 条件类输入,必须走绑定变量,不能依赖
DBMS_ASSERT - 调用前需确认输入已从不可信通道(如 HTTP 参数、UTL_HTTP 请求体)剥离
DBMS_SQL 必须搭配绑定变量,且类型声明要严格
动态 SQL 的唯一安全路径是:先用 DBMS_SQL.PARSE 解析固定骨架语句,再用 DBMS_SQL.BIND_VARIABLE 绑定所有外部数据。骨架中绝不出现用户控制的结构部分。
示例安全写法:
v_cursor := DBMS_SQL.OPEN_CURSOR;<br>DBMS_SQL.PARSE(v_cursor, 'SELECT COUNT(*) FROM employees WHERE department_id = :dept_id AND status = :status', DBMS_SQL.NATIVE);<br>DBMS_SQL.BIND_VARIABLE(v_cursor, ':dept_id', p_dept_id);<br>DBMS_SQL.BIND_VARIABLE(v_cursor, ':status', p_status);
- 绑定变量名必须带冒号前缀(
:dept_id),否则DBMS_SQL不识别为占位符 - 数值型变量要用
DBMS_SQL.BIND_VARIABLE,字符型建议用DBMS_SQL.BIND_VARIABLE_CHAR显式指定长度,避免隐式转换引发截断或乱码 - 游标执行后必须显式
DBMS_SQL.CLOSE_CURSOR,否则长期持有句柄会耗尽共享池内存
复杂动态查询优先用静态 SQL + 条件分支,而非硬拼
当业务需要“按需添加 WHERE 条件”时,与其拼接字符串,不如用 PL/SQL 控制流构造不同 SQL 骨架。虽然代码略长,但彻底规避解析层风险。
比如搜索接口支持按 name、email、status 任意组合过滤,应写成:
IF p_name IS NOT NULL AND p_email IS NOT NULL THEN<br> sql_stmt := 'SELECT * FROM users WHERE name = :name AND email = :email';<br>ELSIF p_name IS NOT NULL THEN<br> sql_stmt := 'SELECT * FROM users WHERE name = :name';<br>ELSE<br> sql_stmt := 'SELECT * FROM users WHERE status = :status';<br>END IF;
- 每个分支的 SQL 都是字面量字符串,无运行时拼接
- 所有参数统一走
DBMS_SQL.BIND_VARIABLE - Oracle 对这类静态骨架的硬解析缓存效率远高于反复解析拼接字符串
真正难处理的是“用户自定义字段排序”或“动态列投影”,这种场景必须引入白名单校验(如只允许 ['id', 'name', 'created_at'])配合 DBMS_ASSERT.SIMPLE_SQL_NAME,而不是放行任意标识符。











