当你发现网站访问变慢,数据库CPU持续100%,第一反应可能是业务流量激增,但仔细查看Web服务器日志却发现请求量并没有显著增加。这种情况十有八九是遭遇了针对数据库的CC攻击变种,攻击者通过构造大量触发慢查询的HTTP请求,以极小的流量代价耗尽数据库资源。MySQL慢查询日志就是定位这类攻击最直接的工具。

慢查询日志如何成为攻击溯源的关键

MySQL慢查询日志默认记录执行时间超过long_query_time阈值的SQL语句,这个阈值通常设置为1秒或2秒。在CC攻击场景下,攻击者会刻意构造需要全表扫描、大量JOIN、复杂排序的请求,比如在搜索框输入通配符开头的模糊查询、在排序参数中传入非法字段名导致filesort、或者在分页参数中传入超大页码引发深度offset。这些操作单次请求看似正常,但并发执行时就会产生大量慢查询,迅速占满数据库连接池和CPU资源。开启慢查询日志后,你会看到大量结构相似但参数不同的SQL语句密集出现,这就是攻击指纹。

快速开启和配置慢查询日志

登录MySQL执行以下命令查看当前配置状态:

SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

如果slow_query_log值为OFF,需要临时开启。生产环境建议动态开启避免重启:

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL log_slow_admin_statements = 'ON';

这里把long_query_time设为1秒,同时开启记录未使用索引的查询和慢管理语句。日志文件路径通过slow_query_log_file变量查看,通常位于MySQL数据目录下。注意动态设置对当前已存在的连接不生效,需要重新连接或者等待新连接建立。如果磁盘空间紧张,可以改用表记录方式,设置log_output='TABLE',慢查询会写入mysql.slow_log表,方便直接查询分析。

从慢查询日志中识别攻击特征

拿到慢查询日志后,不要逐条翻看,直接用mysqldumpslow工具做聚合分析。执行以下命令找出出现次数最多的查询模式:

mysqldumpslow -s c -t 20 /var/lib/mysql/mysql-slow.log

参数-s c表示按出现次数排序,-t 20显示前20条。正常业务慢查询通常模式分散,而CC攻击产生的慢查询会有明显的聚集特征。你会看到某类查询在短时间内出现数百甚至上千次,每次执行时间都在数秒以上。进一步分析这些SQL,重点关注几个异常点:查询条件中是否包含LIKE '%xxx'开头的全模糊匹配,是否使用了SELECT *却只用到少量字段,分页LIMIT的offset是否异常巨大,排序字段是否没有索引支撑。攻击者经常利用这些写法制造资源消耗,而正常用户几乎不会这样操作。

结合general_log还原攻击请求

慢查询日志只能看到SQL语句,看不到是哪个HTTP接口触发的。这时需要临时开启general_log来建立请求与SQL的映射关系。开启方式:

SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';

general_log会记录所有连接和执行过的语句,信息量巨大,建议只开启3到5分钟用于采样。从mysql.general_log表中提取数据,按thread_id将HTTP请求参数与慢查询关联。你会发现攻击请求通常集中在少数几个接口上,比如商品搜索、列表排序、报表导出等需要数据库密集操作的端点。请求参数往往带有明显的fuzz特征,参数值长度异常、包含特殊字符、或者枚举式地遍历大量不存在的ID。

分析攻击流量的具体手法

经过对多起实际案例的复盘,利用慢查询实施CC攻击的手法主要有四类。第一类是深度分页攻击,攻击者在列表接口传入page=99999的参数,迫使数据库执行LIMIT 999990,10这样的查询,MySQL需要扫描并丢弃前面所有行才能取到目标数据,offset越大性能越差。第二类是索引失效攻击,在WHERE条件中使用函数包裹索引列,比如WHERE DATE(create_time)='2024-01-01',或者在字符串字段上使用前缀模糊查询LIKE '%keyword',导致全表扫描。第三类是JOIN炸弹攻击,利用多表关联查询时故意传入能产生笛卡尔积的条件,比如不传关联键或者传入空值,让JOIN结果集爆炸式增长。第四类是排序注入攻击,在ORDER BY参数中传入随机字符串或复杂表达式,不仅无法使用索引排序,还会产生大量临时表和文件排序操作。这些攻击的共同特点是单次查询资源消耗大、并发执行时数据库连接数迅速打满、CPU使用率飙升但QPS并不高。

通过pt-query-digest做深度分析

mysqldumpslow适合快速概览,但要精确定位攻击源还需要Percona Toolkit中的pt-query-digest工具。它能分析慢查询日志并生成详细的HTML报告,包括查询的执行时间分布、锁等待时间、行扫描数等关键指标。执行命令:

pt-query-digest /var/lib/mysql/mysql-slow.log --limit=10 --order-by=Query_time:sum > slow_report.txt

报告中重点关注Query_time占比最高的查询指纹。攻击查询通常Rows_examine值极高而Rows_send值很低,说明数据库扫描了大量行但只返回少量结果,这是典型的资源浪费特征。pt-query-digest还会给出每个查询指纹的样例和执行统计,你可以看到某个查询在攻击时段内的执行次数是平时的几十倍甚至上百倍。把这些查询指纹提取出来,到应用代码中反查对应的业务逻辑,就能定位到具体的漏洞接口。

实时监控与自动化处置方案

事后分析只是第一步,建立实时监控和自动化处置机制才能防止再次被攻击。在MySQL层面,可以通过定时查询information_schema.processlist表,统计相同查询模式且执行时间超过阈值的连接数量。当检测到某个查询模式同时有超过20个连接在执行,且平均执行时间超过5秒,就触发告警。脚本示例:

SELECT SUBSTRING(info, 1, 100) AS query_pattern, 
       COUNT(*) AS concurrent_count,
       AVG(time) AS avg_time
FROM information_schema.processlist 
WHERE command='Query' AND time > 5 
GROUP BY query_pattern 
HAVING concurrent_count > 20;

应用层需要实现接口级别的查询频率限制和参数校验。对搜索接口,限制模糊查询的最小关键词长度,禁止纯通配符查询。对列表接口,限制最大页码和每页最大条数。对排序接口,使用白名单校验排序字段,拒绝非法字段名。对JOIN查询,强制传入必要的关联键参数。这些限制措施要同时在前端和后端实现,不能只依赖前端校验。数据库层面还可以针对高风险用户或IP设置资源限制,使用MySQL的max_user_connections和max_queries_per_hour参数控制单个账户的资源消耗上限。

利用索引优化抵御慢查询攻击

攻击者之所以能通过慢查询消耗资源,根本原因是数据库缺乏合适的索引或者索引设计不合理。在完成攻击溯源后,应该针对被利用的查询模式优化索引。使用EXPLAIN分析攻击SQL的执行计划,重点关注type列是否出现ALL全表扫描、Extra列是否出现Using filesort或Using temporary。为WHERE条件中的过滤字段建立联合索引,为排序字段建立覆盖索引避免回表查询。对于确实需要深度分页的业务场景,改用游标分页或者基于主键的延迟关联方案,避免大offset导致的性能问题。索引优化不仅能抵御攻击,还能提升正常业务的查询性能,是一举两得的防御手段。

构建多层防御体系

单靠数据库层面的防护是不够的,需要构建从前端到后端的完整防御链条。在CDN或负载均衡层配置速率限制,对单个IP的请求频率进行控制。在WAF层设置规则拦截包含SQL注入特征的请求参数,虽然慢查询攻击不一定是SQL注入,但很多攻击参数带有明显的注入探测痕迹。应用层实现接口粒度的限流和熔断,当某个接口的慢查询比例超过阈值时自动降级,返回缓存数据或默认结果。数据库连接池配置合理的超时时间和最大连接数,防止慢查询占满所有连接导致正常请求无法获取连接。最后,建立慢查询日志的定期分析机制,每周导出慢查询报告进行review,及时发现新的异常查询模式。

从攻击中沉淀长期优化策略

每次成功防御CC攻击后,都应该把攻击过程中暴露的问题转化为长期优化项。把攻击者用过的查询模式加入慢查询监控基线,后续出现类似模式时自动告警。把验证有效的防护规则固化为代码规范和上线检查项,新功能上线前必须通过慢查询风险评估。定期进行压测,模拟慢查询攻击场景检验防护措施的有效性。建立数据库查询性能的SLA指标,比如99分位查询响应时间不超过500毫秒,超出就需要排查优化。这些措施能让系统在面对后续攻击时具备更强的韧性,而不是每次都从零开始应急响应。