SQL注入异常主要表现为响应时间突变而非报错,应监控SQL模板级P95延迟、执行计划跳变及慢日志中耗时方差,结合pt-query-digest分析query_time_max/avg比值与rows_sent波动,识别盲注特征。

SQL注入异常通常不是报错,而是响应时间突变
真正的SQL注入漏洞在被利用时,往往不抛出SQLSyntaxErrorException或MySQLSyntaxErrorException这类明显错误——攻击者早把语句拼得严丝合缝。你真正该盯住的,是那些「本不该慢却突然变慢」的请求。
比如一个查用户信息的接口,正常响应 12ms,某次传入' OR SLEEP(5) -- 后变成 5012ms;或者用AND (SELECT COUNT(*) FROM information_schema.tables) > 0触发全库表扫描,耗时从 8ms 涨到 1.2s。这种毛刺才是注入活跃的信号。
- 别只监控 HTTP 状态码或日志 ERROR 级别——90% 的盲注不产生错误日志
- 必须对每个 SQL 模板(含占位符位置)单独统计 P95 响应延迟,而非按 URL 聚合
- 注意数据库连接池的阻塞传导:一条慢查询可能让后续 5 个请求排队,掩盖真实注入点
用 pt-query-digest + 慢日志定位可疑 SQL 模板
MySQL 的slow_query_log本身不标记“是否被注入”,但能暴露异常执行模式。关键不是看单条慢 SQL,而是看同一sql_text哈希下,不同参数组合引发的耗时方差。
用 pt-query-digest 分析时,加--group-by fingerprint再按--order-by Query_time:max排序,你会看到类似:
# Query 1: 0.92 QPS, 0.02x concurrency, ID 0xABC123 # Ratio 97% of all queries; 42% of total response time # Attribute total min max avg 95% stddev median # ============ ======= ======= ======= ======= ======= ======= ======= # Query_time 12s 8ms 5.1s 240ms 4.8s 1.6s 112ms # Rows_sent 120 1 120 2.4 120 2.1 1.0
这里Query_time的 max 和 avg 差 20 倍,且Rows_sent极不稳定——说明参数控制了执行路径,正是注入高发特征。
- 确保
long_query_time=0.1(非默认 1s),否则漏掉时间型盲注的试探请求 -
log_queries_not_using_indexes=ON会干扰判断,关掉;它匹配的是索引缺失,不是注入 - PostgreSQL 用户用
pg_stat_statements查max_exec_time / mean_exec_time > 15的 queryid
对比基准性能时,必须固定执行计划
你以为两次EXPLAIN结果一样就安全?错。MySQL 5.7+ 的optimizer_switch、直方图统计、甚至临时表引擎(tmp_table_size)都可能导致同一条 SQL 在不同参数下走完全不同路径——而攻击者就靠这个触发索引失效或全表扫描。
验证方法不是比 SQL 文本,而是比EXPLAIN FORMAT=JSON里的query_block->table->access_type和used_columns字段。
- 对每个业务 SQL 模板,存一份「干净参数」下的
EXPLAIN JSON快照作为基准 - 线上采集到可疑请求后,用相同参数重放并比对 JSON 中
rows_examined和used_key_parts是否突变 - 警惕
access_type: ALL出现在原本是ref的位置——哪怕只多扫 1 行,也可能被放大成百万级扫描
WAF 日志里sqlmap特征只是冰山一角
依赖sqlmap -r req.txt生成的规则去匹配 WAF 日志?太晚了。真实攻击者早换了手法:SELECT/**/1绕过空格检测、CONCAT(0x73,0x65,0x6c)编码关键字、甚至用information_schema.PROCESSLIST反向探测你的监控粒度。
更有效的做法,是从数据库侧抓「非常规访问模式」:比如一个只读服务突然出现INSERT INTO tmp_xxx SELECT ...,或某个低频接口在凌晨 3 点连续 17 次调用SELECT COUNT(*) FROM mysql.user。
- 开启
general_log不现实,但可对performance_schema.events_statements_history_long设条件轮询:查sql_text LIKE '%information_schema%'且timer_wait > 10000000000(10s) - 不要只过滤
UNION SELECT——ORDER BY (SELECT 1 FROM dual WHERE 1=1)同样危险 - 注意应用层 ORM 自动生成的
WHERE id IN (?),当?被替换成1,2,3,(SELECT SLEEP(3))时,数据库解析器仍认为语法合法
性能基线不是静态数字,是带上下文的执行指纹。参数微小变动引发执行计划跳变,比任何报错都更值得拉响警报。











