oracle动态sql注入风险集中在execute immediate和dbms_sql,核心防御是绑定变量(using)、dbms_assert校验对象名、禁用未白名单的utl_*包,并全程追踪用户输入数据流。

直接看 EXECUTE IMMEDIATE 里有没有拼接变量
Oracle 动态 SQL 的注入风险几乎全集中在 EXECUTE IMMEDIATE 和 DBMS_SQL 的使用上,而最危险的写法就是把用户输入直接拼进字符串。比如:
DECLARE v_user VARCHAR2(100) := 'admin'' OR ''1''=''1'; sql_stmt VARCHAR2(500); BEGIN sql_stmt := 'SELECT * FROM users WHERE username = ''' || v_user || ''''; EXECUTE IMMEDIATE sql_stmt; -- ⚠️ 这里已注入 END;
只要字符串里出现 || 拼接、'''' 手动转义、或用 REPLACE/REPLACE 做“简单过滤”,基本就等于开了门。真实业务里常见错误包括:用 REPLACE(v_input, '''', '''''') 试图防单引号——这在嵌套引号或 Unicode 变体下极易绕过。
- 优先改用绑定变量:
EXECUTE IMMEDIATE 'SELECT * FROM users WHERE username = :1' USING v_user; -
USING子句只支持值绑定,不支持表名/列名/排序字段等结构参数——这些必须走白名单校验或DBMS_ASSERT - 如果必须拼接对象名,用
DBMS_ASSERT.SIMPLE_SQL_NAME或DBMS_ASSERT.QUALIFIED_SQL_NAME校验,别自己写正则
检查 DBMS_SQL 是否跳过 DEFINE_COLUMN 和 BIND_VARIABLE
DBMS_SQL 比 EXECUTE IMMEDIATE 更底层,也更容易漏掉安全环节。典型风险是:打开游标后直接 PARSE 拼接语句,然后调用 EXECUTE,却没对输入参数做 BIND_VARIABLE。
错误示例:
v_cursor := DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE(v_cursor, 'DELETE FROM ' || table_name || ' WHERE id = ' || user_id, DBMS_SQL.NATIVE); DBMS_SQL.EXECUTE(v_cursor); -- ❌ user_id 是数字但没绑定,table_name 更没校验
正确做法分两步:
- 结构部分(如表名)必须经
DBMS_ASSERT或硬编码白名单控制 - 数据部分(如
WHERE条件值)一律走DBMS_SQL.BIND_VARIABLE(v_cursor, ':id', user_id),再在PARSE字符串中写:id - 查询结果列要用
DEFINE_COLUMN显式声明类型和长度,避免隐式转换引发截断或溢出
警惕 UTL_HTTP、UTL_FILE 等包被当“执行通道”滥用
有些团队以为不用 EXECUTE IMMEDIATE 就安全,结果把动态 SQL 换成调用外部 HTTP 接口或写临时文件再读取执行——这反而扩大了攻击面。比如:
v_url := 'http://internal-api/?q=SELECT%20*%20FROM%20' || user_table; v_resp := UTL_HTTP.REQUEST(v_url); -- ❌ 把拼接逻辑外包给另一个服务,没解决根本问题
这类写法本质是把注入点从 PL/SQL 移到了下游服务,且更难审计。真正要防的是“任意用户可控输入参与 SQL 构建”这个行为本身,不管中间经过多少层封装。
- 禁用所有未经白名单限制的
UTL_*包调用,尤其涉及网络或文件系统操作 - 若必须用,确保传入参数经过
DBMS_ASSERT或应用层严格格式校验(如仅允许字母+下划线,长度≤30) - 数据库防火墙(如 Oracle Database Firewall)能捕获这类外发请求对应的 SQL 流量,但前提是开启代理模式并加载规则库
静态扫描时重点盯 EXECUTE IMMEDIATE 后的字符串变量来源
工具扫描容易漏掉间接拼接。比如变量先赋值给中间变量,再传给 EXECUTE IMMEDIATE,或者从包变量、上下文(sys_context)读取——这些都不会被简单 grep || 发现。
人工审计或高级 SAST 工具需跟踪变量数据流:
- 从
EXECUTE IMMEDIATE往前追溯,直到源头(如owa_util.get_cgi_env、APEX_APPLICATION.G_X01、pkg_input.get_value) - 确认中间是否经过任何“过滤函数”:自定义的
clean_sql_input几乎都不可信,除非它内部用DBMS_ASSERT或绑定变量重写逻辑 - 特别注意 APEX 应用:大量
&P1_ITEM.替换、htp.p输出拼接,都是高危区
复杂点在于,Oracle 没有统一的“参数化查询 API”像 JDBC 那样强制约束,所以防御完全依赖开发者对 USING、DBMS_ASSERT、绑定上下文的主动选择——而人总会忘记。











