双引号标识符本身安全,但用户输入未经白名单校验直接拼入双引号会引发SQL注入,因其绕过标识符合法性检查且PreparedStatement无法绑定表名等标识符。

Oracle中用双引号包裹标识符(如表名、列名)本身不构成注入风险,但若把用户输入直接拼进双引号内,就等于把控制权交给了攻击者——"user_name" 安全,"user_name; DROP TABLE users--" 就不是。
为什么双引号标识符会引发注入?
Oracle允许用双引号定义大小写敏感或含特殊字符的标识符,比如 "User Name" 或 "order"(绕过保留字限制)。但数据库不会校验双引号内的内容是否合法标识符——它只负责按字面解析。一旦你把未经清洗的用户输入塞进双引号,攻击者就能构造出任意合法(甚至非法但被忽略)的标识符,配合其他SQL片段实现注入。
- 常见错误现象:
SELECT * FROM " + userInput + " WHERE id = 1,当userInput是users" UNION SELECT password FROM admin --,最终语句变成SELECT * FROM "users" UNION SELECT password FROM admin --" WHERE id = 1,注释掉后半截,成功执行恶意查询 - 根本原因:双引号仅关闭“标识符解析”,不关闭“SQL语法解析”;拼接发生在应用层,Oracle在执行时已无从区分哪部分是原始逻辑、哪部分是用户污染
- 注意:这种场景和单引号字符串注入不同——它不依赖字符串闭合,而是利用标识符边界+SQL结构重组合
严格校验输入比转义更可靠
对双引号内的标识符做“转义”没有标准方案(Oracle不支持 "order\" 这类写法),强行替换或截断反而破坏业务语义。唯一安全路径是白名单校验:
- 提取用户输入后,先用正则
^[a-zA-Z][a-zA-Z0-9$_#]*$判断是否符合Oracle标识符命名规则(首字母、后续字符范围、长度≤30) - 再查系统视图确认该标识符真实存在:
SELECT 1 FROM all_tables WHERE table_name = UPPER(?)(注意大小写:双引号标识符存为原样,未加引号的默认大写) - 禁止接受含空格、短横线、点号等必须用双引号的字符——这类需求本身就该由配置项或映射表承接,而非直传SQL
- 若必须支持大小写混合名,强制要求前端/配置中统一用小写存储,查询时
UPPER(?)后匹配all_objects.object_name
用PreparedStatement无法解决标识符拼接问题
PreparedStatement 只能绑定**值**(? 占位符),不能绑定表名、列名、排序字段等**标识符**。下面写法在JDBC中会直接报错:
String sql = "SELECT * FROM ? WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "users"); // ❌ 运行时报 ORA-00903: invalid table name ps.setInt(2, 123);
- 正确做法:校验通过后,用字符串拼接生成SQL,但拼接前必须完成上一步的双重校验(格式+存在性)
- 不要试图用
chr(34)或动态拼"' + userInput + '"来“模拟”双引号——这只会让校验逻辑更脆弱 - 如果业务频繁需要动态表名,应重构为视图或同义词,把动态部分收口到DBA可控范围内
最容易被忽略的大小写陷阱
Oracle对双引号标识符严格区分大小写,而未加引号的标识符默认转为大写。这意味着:
-
SELECT * FROM "Users"和SELECT * FROM "users"是两个不同的表 - 校验存在性时,若用户传
"users",你用UPPER("users")查all_tables.table_name会查不到(因为该字段存的是原样users,不是USERS) - 更隐蔽的问题:某些ORM或连接池会自动把未引号标识符转大写后再发给Oracle,导致应用层看到的“一致”其实是假象
- 建议:所有校验逻辑统一用
DBMS_ASSERT.SIMPLE_SQL_NAME函数(Oracle内置),它会在数据库端做原子化检查并抛出异常,比应用层正则更权威


















