Oracle中SQL注入修复必须用绑定变量(USING子句),动态SQL字符串须完全静态,结构名需DBMS_ASSERT校验,权限应遵循最小原则。

Oracle 应用中 SQL 注入风险,只要用了 EXECUTE IMMEDIATE 或 OPEN FOR 拼接用户输入,就基本等于开了后门——修复不是“要不要做”,而是“必须立刻改”。唯一有效解法是绑定变量(USING 子句),其他手段(过滤、转义、正则)全是临时止痛药,挡不住真实攻击。
EXECUTE IMMEDIATE 必须配 USING,不能拼字符串
这是最常出事的入口。动态 SQL 的字符串部分必须完全静态,所有外部值只能走 USING 传入。
- ❌ 错误写法:
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = ''' || user_name || '''';—— 单引号被绕过、Unicode 编码、注释符组合全无效防住 - ✅ 正确写法:
EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE name = :n' USING user_name;——:n是占位符,user_name是 PL/SQL 变量,Oracle 在执行前统一做类型检查和转义 - ⚠️ 注意:
USING后面不能跟表达式,比如USING UPPER(name)会报错;必须先赋值:l_name := UPPER(name); EXECUTE IMMEDIATE ... USING l_name;
IN 子句不能直接绑数组,得用 MEMBER OF 或固定占位符
想让 WHERE id IN (:1, :2, :3) 动态适配任意长度列表?Oracle 不支持。硬拼字符串或试图 USING my_list 都会触发 ORA-01008: not all variables bound。
- ✅ 安全做法一(推荐):用嵌套表 +
MEMBER OFSELECT * FROM orders WHERE order_id MEMBER OF :id_list;
绑定时传sys.odcinumberlist(101, 102, 103) - ✅ 安全做法二(简单场景):预设最大数量(如 10 个),SQL 写死
WHERE id IN (:1,:2,...,:10),未用参数传NULL,再加AND id IS NOT NULL过滤 - ❌ 千万别:
LISTAGG拼字符串再用INSTR查——这又回到字符串拼接,高危区
表名、列名等结构信息不能绑,但也不能裸拼
绑定变量只适用于值(WHERE 条件、SET 赋值),不适用于对象名。但直接拼 user_table 或 order_col 同样危险。
- ✅ 正确姿势:
DBMS_ASSERT.SIMPLE_SQL_NAME校验后拼接safe_table := DBMS_ASSERT.SIMPLE_SQL_NAME(p_table_name);EXECUTE IMMEDIATE 'SELECT * FROM ' || safe_table; - ⚠️ 关键限制:
SIMPLE_SQL_NAME只允许字母、数字、_、$、#,长度 ≤ 30,且不是 Oracle 保留字;它不处理空格、排序方向(ASC/DESC)、点号分隔,这些要拆开单独校验 - ❌ 别用
DBMS_ASSERT.ENQUOTE_NAME当校验函数——它只加双引号,不校验合法性;也别用NOOP,那是调试陷阱
绑定变量不是万能的,权限和上下文一样关键
即使 USING 写得完全正确,如果存储过程用的是定义者权限(DEFINER’S RIGHTS),且账号有 DROP ANY TABLE 这类高权,攻击者仍可能通过合法查询触发破坏性操作。
- 务必按最小权限原则分配数据库账号:Web 应用只授
SELECT、INSERT等必要权限,禁用EXECUTE系统包(除非明确需要) - 对含动态 SQL 的存储过程,优先用调用者权限(
AUTHID CURRENT_USER),让执行权限随调用者身份变化,避免“一个漏洞毁全库” - DBMS_ASSERT 和绑定变量解决的是“输入是否被当指令执行”,但解决不了“这个指令本身是否该被允许执行”——后者靠权限控制
真正难的不是写对 USING,而是在业务逻辑里识别哪些地方“看起来安全实则危险”:比如把用户输入先 REPLACE 单引号再拼,或者用 DBMS_ASSERT 处理密码字段——这种伪安全比明摆着拼接更可怕,因为它让人放松警惕。


















