PreparedStatement在Oracle中必须“全程真用”:表名、列名等结构元素无法绑定,须白名单校验;${}拼接、EXECUTE IMMEDIATE、权限配置任一疏漏均导致防护失效。

用 PreparedStatement 是必须的,但光写个 prepareStatement() 调用根本不够——Oracle 场景下,表名、EXECUTE IMMEDIATE、${} 拼接、权限配置这四点漏一个,就可能被绕过。
为什么 PreparedStatement 在 Oracle 里也得“全程真用”
Oracle JDBC 驱动对 PreparedStatement 的绑定机制是可靠的,但它只保护 ? 占位符位置上的值。一旦你在 SQL 字符串里拼了变量,哪怕只拼了一个表名,整个防护就失效了。
- ✅ 正确:
"SELECT * FROM user_log WHERE id = ? AND status = ?"+ps.setLong(1, id)+ps.setString(2, status) - ❌ 错误:
"SELECT * FROM " + tableName + " WHERE id = ?"—— 这里tableName是用户可控的,攻击者传入user_log WHERE 1=1 UNION SELECT password FROM dba_users --就直接触发注入 - ⚠️ 注意:
setString()不做字符过滤,它靠的是数据库端的绑定语义;单引号、分号、--全部原样传入,但不会被解析为 SQL 语法
Oracle 动态表名/列名必须走白名单校验
Oracle 不支持 ORDER BY ? 或 FROM ?,这类结构在预编译阶段就会抛 SQLException: Parameter index out of range。硬拼字符串是唯一技术路径,但必须加白名单卡死。
- 拒绝任何正则替换、黑名单过滤(如删
;或UNION)——Oracle 支持 Unicode 注释、嵌套注释、PL/SQL 块,规则永远追不上变体 - 用枚举或静态 Map 映射合法输入:
Map.of("order", "t_order_202610", "user", "t_user_active"),查不到就抛IllegalArgumentException - 配置文件里定义允许值也可以,但禁止从 request 参数直读后进 switch/case —— 必须先查表、再取值、再拼 SQL
MyBatis 和 PL/SQL 里的 ${}、EXECUTE IMMEDIATE 是高危盲区
${} 在 MyBatis 中等于字符串拼接,EXECUTE IMMEDIATE 在 PL/SQL 中等价于 Java 里的 Statement.execute() —— 它们完全绕过 PreparedStatement 保护。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- ❌ 禁止:
@Select("SELECT * FROM ${table} WHERE id = #{id}"),其中${table}绑定用户输入 - ✅ 可行:
${@TBL_PREFIX@}_user这种硬编码前缀,或通过+#{}构造安全值(如模糊查询的#{pattern}) - ⚠️ PL/SQL 中若必须动态执行,先用
DBMS_ASSERT.SQL_OBJECT_NAME校验对象名,再拼接;别信“我只拼字母数字”——Oracle 里CHR(39)就是单引号
连接配置和数据库权限是最后一道卡口
即使代码层某处漏了校验,收紧权限也能让攻击者查不到敏感表、改不了系统参数、执行不了高危包。
- JDBC URL 加上
allowMultiQueries=false(防堆叠注入)、oracle.jdbc.useFetchSizeWithLongColumn=true(避免某些驱动 bug 导致绑定失效) - 应用账号禁用:
DROP ANY TABLE、ALTER SYSTEM、UTL_HTTP、DBMS_LOB等非业务必需权限 - 生产环境绝对关闭
show_sql=true,日志里泄露的完整 SQL 可能包含脱敏失败的参数
最常被忽略的是:白名单校验要覆盖所有动态拼接点,包括排序字段、IN 子句的列名、甚至 WHERE 条件里的字段名——这些地方不报错,但拼错就等于开后门。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










