参数化查询是唯一可靠解法,它让数据库提前编译SQL结构、后期仅绑定数据值,?占位符不被解释为语法;表名、字段名、排序方向等无法参数化的位置必须用白名单校验。

修复方案必须绕开“参数拼接”这个根本漏洞点
所有无回显盲注能成功,前提都是后端把用户输入直接拼进 SQL 字符串里,比如 String sql = "SELECT * FROM user WHERE id = '" + request.getParameter("id") + "'";。修复不是加个过滤或 WAF 就完事,而是要从执行机制上切断拼接路径。参数化查询(Prepared Statement)是唯一可靠解法,它让数据库引擎提前编译 SQL 结构、后期只绑定数据值,? 占位符不会被解释为语法的一部分。
Java 项目中 PreparedStatement 的正确写法与常见误用
很多开发以为用了 PreparedStatement 就安全了,结果还是出问题。关键在占位符不能出现在表名、字段名、排序方向这些语法位置上——这些地方无法参数化,只能靠白名单校验。
- ✅ 正确:WHERE 条件、IN 子句、LIMIT 值都用
?绑定,如SELECT * FROM user WHERE status = ? AND age > ? - ❌ 错误:用字符串拼接构造表名,如
"SELECT * FROM " + tableName + " WHERE id = ?"—— 这里tableName必须从预设白名单中取,比如Map.of("user", "t_user", "order", "t_order") - ⚠️ 注意:MySQL 的
ORDER BY ?会报错,必须用if ("asc".equals(sort)) { sql += " ORDER BY name ASC"; }这类逻辑分支,且字段名也需白名单限制
修复后必须验证的三个盲注残留点
即使上了 PreparedStatement,仍可能因其他环节引入漏洞。测试时重点盯这三处:
- 日志打印:是否把原始请求参数(尤其是
id=1' OR '1'='1)原样记入日志?一旦日志被攻击者读取(如通过路径遍历),等于变相回显 - 错误页面:是否在生产环境返回了含 SQL 片段的堆栈(如
java.sql.SQLException: You have an error in your SQL syntax...)?这类信息应统一转为500 Internal Server Error,不带任何上下文 - 数据库权限:应用连接数据库用的账号是否拥有
information_schema查询权?应收缩为仅对业务库的SELECT/INSERT/UPDATE,禁用SHOW DATABASES、LOAD_FILE等高危权限
上线前必须跑通的最小回归测试用例
修复不是改完代码就结束,得用真实盲注 payload 反向验证是否真断了链路。别依赖“没报错就算修好”,要确认响应行为完全失去布尔或时间差异:
- 发
id=1' AND SLEEP(3)--+,检查响应时间是否稳定在毫秒级(不是 3 秒左右) - 发
id=1' AND (SELECT COUNT(*) FROM information_schema.tables) > 0--+,检查返回页面内容是否与id=1完全一致(不能出现空白页或报错页) - 发
id=1' AND ASCII(SUBSTR((SELECT database()),1,1))=100--+,对比=97和=100两次请求,响应体哈希值必须相同
真正难的不是写修复代码,而是让所有开发理解:只要还存在一处字符串拼接 SQL 的调用,整个防护就形同虚设。这点在多层 service 调用、动态查询构建、旧代码迁移时最容易被忽略。










