dbms_assert不是sql注入通用防护,仅校验sql标识符;simple_sql_name校验纯标识符,qualified_sql_name校验schema.table,enquote_*仅加引号不校验,均不能替代绑定变量。

DBMS_ASSERT 不是 SQL 注入的通用防护开关,它只在「动态拼接表名、列名等 SQL 标识符」时才真正起效;对 WHERE 条件值、用户输入内容完全无效,强行用它处理业务字段等于放弃防御。
DBMS_ASSERT.SIMPLE_SQL_NAME 适合校验纯标识符字段名
这个函数只接受字母、数字、下划线、$、#,长度 ≤ 30,且不能是 Oracle 保留字。它适合校验 ORDER BY 字段、GROUP BY 列、AS 别名这类纯结构名称。
- ✅ 正确用法:
DBMS_ASSERT.SIMPLE_SQL_NAME('salary')→ 返回'salary' - ❌ 错误用法:
DBMS_ASSERT.SIMPLE_SQL_NAME('salary ASC')→ 报 ORA-44003(含空格和关键字) - ❌ 错误用法:
DBMS_ASSERT.SIMPLE_SQL_NAME('user-id')→ 连字符不合法,直接报错 - ⚠️ 注意:它不接受双引号包裹的名称,
DBMS_ASSERT.SIMPLE_SQL_NAME('"name"')会失败
DBMS_ASSERT.QUALIFIED_SQL_NAME 用于 schema.table 场景
当你需要支持类似 hr.employees 这样的带点分隔的对象引用时,该函数会逐段校验——每一段都必须满足 SIMPLE_SQL_NAME 规则。
- ✅ 正确:
DBMS_ASSERT.QUALIFIED_SQL_NAME('scott.emp') - ❌ 错误:
DBMS_ASSERT.QUALIFIED_SQL_NAME('SCOTT."EMPLOYEES"')→ 双引号不被允许 - ❌ 错误:
DBMS_ASSERT.QUALIFIED_SQL_NAME('tenant_abc.orders')→ 如果tenant_abc是合法 schema 名,但未在数据库中真实存在,它仍会通过(除非你用SQL_OBJECT_NAME) - ⚠️ 注意:它不做对象存在性检查,仅做语法合法性断言
DBMS_ASSERT.ENQUOTE_LITERAL 和 ENQUOTE_NAME 容易被误用
这两个函数不是校验器,而是“加引号工具”。它们不拦攻击,只负责格式化输出,常被当成防护手段误用。
-
DBMS_ASSERT.ENQUOTE_LITERAL('O''Reilly')→ 返回'O''Reilly'(自动转义单引号),可用于构造安全字面量,但**不能替代绑定变量** -
DBMS_ASSERT.ENQUOTE_NAME('emp')→ 返回"emp",仅加双引号,DBMS_ASSERT.ENQUOTE_NAME('emp"; DROP TABLE users --')会原样返回"emp"; DROP TABLE users --",毫无防护力 - ⚠️ 关键区别:
ENQUOTE_*类函数从不抛异常,只做字符串变换;而SIMPLE_SQL_NAME等校验函数会在非法时立即报 ORA-44003
为什么 DBMS_ASSERT 无法替代绑定变量
根本原因在于:DBMS_ASSERT 在 PL/SQL 层做字符串断言,不参与 SQL 解析阶段。一旦它放行,后续拼接仍是动态 SQL,攻击者可能利用合法标识符组合逻辑漏洞。
- ❌ 危险写法:
EXECUTE IMMEDIATE 'SELECT * FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_table) || ' WHERE name = ''' || p_name || '''';——p_name仍被拼接,完全暴露 - ✅ 正确组合:
EXECUTE IMMEDIATE 'SELECT * FROM ' || DBMS_ASSERT.SIMPLE_SQL_NAME(p_table) || ' WHERE name = :n' USING p_name; - ⚠️ 最容易被忽略的一点:DBMS_ASSERT 对
IN子句中的值列表、LIMIT/OFFSET数值、排序方向(ASC/DESC)等均无能为力,这些必须靠白名单或预置枚举控制
真正难的不是调用哪个函数,而是分清哪里是“结构”、哪里是“值”——表名可校验,用户名必须绑定,而“按什么字段排序”要拆成字段名(校验)+ 方向(白名单判断),三者混用一个函数就等于没防。











