应通过行为密度(如2分钟内高频相似语句)、上下文偏离(同一ip/账号连续超10条含or 1=1等payload)及基线对比(查询量、敏感词占比、行数标准差突增)识别批量sql注入,而非仅依赖关键字匹配。

审计日志里怎么筛出批量注入的可疑 SQL?
关键不是“有没有 UNION SELECT”,而是看行为密度和上下文偏离。攻击者扫库时往往在短时间(比如 2 分钟内)高频执行结构相似、仅参数变化的语句,例如:
SELECT * FROM users WHERE id = 1 OR 1=1 SELECT * FROM users WHERE id = 2 OR 1=1 SELECT * FROM users WHERE id = 3 OR 1=1
这类请求在日志中表现为:
-
event_time高度密集(间隔 -
argument字段匹配正则OR\s+1\s<em>=\s</em>1|UNION\s+SELECT|SLEEP(|BENCHMARK(|/*.<em>?\</em>/ - 同一
user_host或client_addr下连续出现 >10 条
MySQL 的 mysql.general_log 表默认无索引,直接 LIKE '%union%' 会卡死。应改用:
- 先加时间过滤:
WHERE event_time > NOW() - INTERVAL 2 MINUTE - 再用
REGEXP匹配多形态 payload:argument REGEXP "(OR[[:space:]]+1[[:space:]]<em>=[[:space:]]</em>1|union[[:space:]]+select|sleep\()" - 最后加
LIMIT 100控制返回量
PostgreSQL pgAudit 怎么才能看到真实注入 payload?
默认开 pgaudit 只能看到 WHERE id = ?,攻击者的真实语句(如 WHERE id = 1; DROP TABLE users;)被完全隐藏。必须改两个配置项:
-
pgaudit.log_parameter = on:否则所有绑定参数都脱敏为问号 -
pgaudit.log_catalog = off:否则SELECT * FROM pg_class类系统查询刷屏,业务语句被淹没
改完需重启 PostgreSQL 才生效。另外注意:pgaudit.log 默认只记录会话级事件,若攻击走连接池(如 pgbouncer),client_addr 会固定为池地址——得在 pgbouncer.ini 中设 ignore_startup_parameters = application_name,并让应用显式传 application_name 标识真实来源。
告警规则不能只盯单条语句,得建行为基线
单看一条 SLEEP(5) 可能是运维脚本;但同一账号 1 小时内执行 47 次带 SLEEP 的查询,且全部命中敏感表 customer,这就构成高危事件。需要:
- 按
user+client_addr+database维度聚合统计 5 分钟窗口内的:- 查询总条数(正常人一般 200)
- 含高危关键词语句占比(> 30% 即异常)
- 返回行数标准差(批量扒数据时行数波动极小)
- 对比历史基线:比如该账号过去 7 天平均每天查
users表 3 次,今天已查 86 次,自动触发告警
别用静态关键字黑名单——攻击者把 UNION SELECT 写成 UNI/<strong>/ON SEL/</strong>/ECT 就能绕过。
审计日志本身不能拦截,但能联动阻断
数据库审计日志是只读证据源,不参与请求处理链路。真要拦截,得靠三层协同:
- 数据库层:开启
audit_log_policy = ALL(MySQL)或pgaudit.log = 'read, write, ddl'(PG),确保原始流量不丢 - 中间件层:用 Lua 脚本(Nginx)或代理插件(如 MySQL Router)解析审计输出,识别出高危模式后主动断连或限流
- 运维层:收到告警后,立即调用 API 执行
KILL CONNECTION <id></id>或临时禁用该账号
最容易被忽略的是权限闭环:审计日志文件(如 /var/lib/mysql/audit.log)默认属主为 root:root,监控程序若非 root 权限就读不了;而日志路径若没配 audit_log_rotate_on_size,单次爆破可能撑爆磁盘导致审计静默失效。











