SQL注入防护是否生效不能仅看是否使用PreparedStatement,拼接表名列名、ORM混用字符串拼接、ORDER BY字段、动态库名、JSON内嵌SQL等均可能绕过;mysql_real_escape_string等转义函数无法防御宽字节、十六进制及数字上下文攻击;验证需手动测试SLEEP、UNION查询并检查实际执行SQL与日志。

SQL注入防护是否生效,不能只看代码有没有用 PreparedStatement
写了预编译、加了参数化查询,不等于防住了——真实攻击链里,绕过点往往在你没盯住的地方。比如拼接了表名、列名,或用了 String.format 包裹用户输入再塞进 SQL;又或者 ORM 框架里混用了 createSQLQuery + 字符串拼接。这些地方,PreparedStatement 根本不生效。
白帽子验证时必测的三个非标准入口
渗透测试不是跑个 ' OR 1=1 -- 就完事。真正容易漏掉的是那些“看起来不像 SQL”的位置:
-
ORDER BY后的字段名:比如/api/list?sort=user_name%20ASC, id DESC—— 这里如果直接拼进 SQL,user_name ASC, (SELECT password FROM users LIMIT 1)就可能执行 - 动态表名/库名:常见于多租户系统,用
tenant_id拼出logs_2024表,若未校验白名单,可被替换成logs_2024; DROP TABLE users-- - JSON 字段内嵌 SQL 片段:某些搜索接口接受 JSON body,其中
filter字段值被反序列化后参与构建查询,而解析逻辑没做键名/值内容过滤
mysql_real_escape_string 或 addslashes 不是解药
这类函数只对单引号、反斜杠等做转义,但完全挡不住宽字节注入、十六进制绕过、或在数字上下文(如 id=1 AND SLEEP(5))中直接执行。更麻烦的是:MySQL 8.0+ 默认关闭 sql_mode=NO_BACKSLASH_ESCAPES,导致 addslashes 反而引入新漏洞。实际测试中,用 id=1%20UNION%20SELECT%201,2,3 直接打比用 \' 更容易触发。
验证有效性的最小可行动作清单
别依赖扫描器报告。自己动手验证,每个动作都要观察数据库实际行为:
- 在目标参数位置依次尝试:
1' AND SLEEP(1)--、1 AND (SELECT COUNT(*) FROM information_schema.tables) > 0、1 UNION SELECT @@version, user(), database() - 检查响应时间是否明显延迟(确认
SLEEP执行),或返回结果是否包含数据库元信息 - 抓包看后端是否真把输入当参数传给了
executeQuery,而不是先拼成字符串再交给Statement - 查日志:开启 MySQL 的
general_log,看最终执行的 SQL 是SELECT * FROM users WHERE id = ?还是SELECT * FROM users WHERE id = '1 UNION ...'
最常被忽略的一点:应用层做了过滤,但中间件(如 Nginx、API 网关)或缓存层(Redis Lua 脚本)又把原始参数透传给了下游。防护必须端到端对齐,不能只盯 Java 或 Python 层。










