直接替换为preparedstatement是唯一可靠解法,必须逐处改造老旧系统中字符串拼接sql的statement代码,因注入发生在sql解析阶段,java层过滤无效;mybatis中禁用${},动态表名/列名须白名单校验。

直接替换为 PreparedStatement 是唯一可靠解法
老旧系统里大量 Statement 拼接 SQL 的代码,不能靠“加过滤”“加正则”“加日志”来治,必须逐处替换为 PreparedStatement。因为注入发生在 SQL 解析阶段,任何 Java 层的字符串清洗都晚于数据库引擎对语句结构的判定——攻击者输入的 ' OR 1=1 -- 在进入数据库前已是完整语法片段,过滤器根本来不及干预。
实操建议:
- 优先从登录、搜索、订单查询等用户可控参数入口开始改造,这类接口最常被扫描利用
- 替换时注意占位符顺序与
setXxx()调用顺序严格一致,错位会导致字段值写反(如把密码设成用户名) - 不要试图复用同一个
PreparedStatement实例跨不同 SQL 模板——它绑定的是预编译后的语句结构,换 SQL 字符串必须新建实例 - 原
Statement.executeQuery("SELECT * FROM user WHERE id = " + id)必须拆成两步:先定义带?的 SQL 字符串,再用setInt(1, id)
MyBatis 项目中误用 ${} 是高频雷区
很多“半老旧”系统已迁到 MyBatis,但开发者仍习惯用 ${} 拼接动态表名或排序字段,这等同于回到 Statement 时代。例如 ORDER BY ${sortField} 或 FROM ${tableName},一旦 sortField 来自前端请求,就完全开放注入面。
安全做法:
-
#{}是默认且安全的选择,所有参数一律走它 - 真需要动态表名/列名时,必须白名单校验:
if (!Arrays.asList("user", "order", "product").contains(tableName)) throw new IllegalArgumentException(); - 排序字段限制为固定枚举:
switch (sortField) { case "name": return "u.name"; case "created_time": return "u.created_time"; default: throw new IllegalArgumentException(); } - 绝对不要在 XML 中写
WHERE ${condition}这类兜底拼接逻辑
遗留代码里隐藏的 Statement 难以发现
老旧系统常存在“看不见的 Statement”:比如封装了 JdbcUtils 工具类,表面是 query(String sql, Object... args),内部却用 String.format() 或 StringBuilder 拼接后交给 Statement 执行;或者 DAO 层方法签名看似接受参数,实际在实现里做了字符串替换。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
排查重点:
- 全局搜索
createStatement()、executeQuery((带字符串参数)、executeUpdate((带字符串参数) - 检查所有自定义 JDBC 工具类,尤其关注
execute、query、update等通用方法的底层实现 - 留意日志中打印出的原始 SQL——如果看到类似
WHERE name = 'admin' OR '1'='1'这种明显被篡改的语句,说明某处仍在拼接 - 对反射调用或动态代理生成的 DAO 方法,需反编译确认其 SQL 构建逻辑
兼容性与性能误区要避开
有人担心替换 PreparedStatement 会影响性能或兼容老数据库驱动,其实恰恰相反:现代 MySQL、PostgreSQL 驱动对预编译有优化,重复执行同结构 SQL 时比 Statement 更快;而 Oracle 8i+、SQL Server 2005+ 全支持标准 JDBC 4.0,无需额外配置。
常见误操作:
- 给每个查询都加
connection.prepareStatement(sql)却不复用——应按 SQL 模板缓存PreparedStatement实例(注意线程安全) - 用
setString()处理数字字段(如id),虽能运行但可能触发隐式类型转换,改用setInt()或setLong() - 忽略
try-with-resources,导致PreparedStatement和ResultSet未关闭——旧系统资源泄漏会更快暴露 - 以为加了
Filter过滤OR 1=1就安全了,但攻击者可绕过:用注释符--、编码绕过(%27)、或 Unicode 变体(′替代')
真正难的不是改几行代码,而是识别那些藏在工具类、脚本化 SQL、甚至存储过程调用里的拼接逻辑——它们往往没有明显关键字,只能靠 SQL 日志回溯和参数污染测试来揪出。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










