预处理语句绕过主因是用户输入未走占位符而被拼入sql模板,如动态表名、order by;mybatis ${}、jpa nativequery等跳过参数绑定;waf易被编码绕过且不能替代应用层校验与最小权限控制。

SQL 注入是怎么绕过预处理语句的
预处理语句(PreparedStatement)不是万能盾牌,绕过常发生在「参数没进占位符」或「动态拼接了 SQL 片段」时。比如用字符串拼接表名、字段名、ORDER BY 子句,而这些位置根本不能用 ? 占位。
常见错误现象:java.sql.SQLException: Parameter index out of range 或查询结果异常,但日志里却出现用户输入直接进了 SQL 字符串。
- 只对「值」使用
?,绝不把用户输入塞进 SQL 模板字符串里(如"SELECT * FROM " + tableName + " WHERE id = ?") - 若必须动态指定表名/排序字段,先白名单校验:用
Enum或Map映射合法值,再取键对应真实名称 - MySQL 中
SET @var := ?这类语句不触发预处理绑定,变量仍可被注入,应避免在业务逻辑中混用会话变量
ORM 框架里哪些写法等于裸写 SQL
像 MyBatis 的 <script></script>、${} 插值、JPA 的 @Query(nativeQuery = true) 都是高危区——它们跳过了框架的参数绑定机制,等价于手动拼接字符串。
使用场景:需要复杂动态条件(如多级嵌套 OR)、数据库特有函数(JSON_CONTAINS)、或分库分表路由逻辑。
-
${}只用于已知安全的常量(如固定枚举字段名),且必须配合白名单校验逻辑,不能直接接request.getParameter("sort") - MyBatis
<foreach></foreach>生成 IN 列表时,确保传入的是List<string></string>而非拼好的字符串;否则#{}也救不了 - JPA 自定义查询若含用户输入,优先改用
CriteriaBuilder或拆成多个@Query+ 条件分支,而不是硬写 native SQL
为什么加了 WAF 还被拖库
WAF 规则靠模式匹配,对编码绕过(如双 URL 编码、宽字节注入)、HTTP 参数污染(id=1&id=1' AND SLEEP(5)--)、或低频慢速注入(IF(SUBSTRING(@@version,1,1)='5',SLEEP(3),0))识别率很低。
性能影响:过度依赖 WAF 会增加首字节延迟,尤其在高并发查询场景下,还可能误杀合法请求(如含 union 的报表字段名)。
- WAF 是最后一道网,不是第一道锁;它不能替代应用层参数校验和最小权限 DB 账号
- 检查 DB 账号权限:业务账号是否拥有
SELECT以外的FILE、LOAD DATA、CREATE FUNCTION权限 - 开启 MySQL 的
sql_mode=STRICT_TRANS_TABLES,让非法类型隐式转换失败,堵住部分基于报错的注入路径
审计日志里看不出注入,但数据被改了
因为很多注入不走 SELECT,而是用 UPDATE ... WHERE、INSERT ... SELECT 或盲注中的布尔逻辑篡改数据。日志只记语句模板(如 UPDATE users SET status=? WHERE id=?),不记录实际绑定值。
容易被忽略的地方:应用日志通常不记录 SQL 绑定参数,DB 审计日志又默认关闭或只开 DDL,导致操作链断裂。
- MySQL 开启
general_log成本太高,推荐用slow_query_log+long_query_time=0捕获所有查询,再过滤出带用户输入特征的语句(如含UNION、SLEEP、WAITFOR) - 应用层加埋点:在 DAO 方法入口打日志,记录方法名 + 参数对象 toString(),但注意脱敏(如
password=***) - 关键表加触发器监控异常更新(如
status字段被批量设为1),但别用SELECT触发告警——可能被注入利用
事情说清了就结束。









