
暴力破解MySQL密码这件事很多团队一开始都不当回事直到某天发现数据库端口被扫烂、错误日志堆了几万条Access denied甚至业务账号真的被撞库撞穿才急急忙忙来找解决方案。如果你也是这种状态或者你想在问题发生之前先把防线补上MySQL官方自带的Connection Control插件就是目前性价比最高、也最省事的一个选择。这篇文章就把它的原理、安装、调参、踩坑和周边协同一次讲透。1. 暴力破解思路与Connection Control的设计初衷1.1 为什么防火墙和WAF挡不住针对MySQL的暴力破解很多人的第一反应是我服务器有安全组有防火墙有WAF怎么还会被暴力破解这里有个认知误区。安全组和防火墙确实能挡住绝大部分来自外部的扫描但MySQL的3306端口一旦对公网开放或者内网有被攻陷的跳板机攻击者的请求从源头上就是合法连接。WAF更多防护的是HTTP层的应用攻击对数据库这种协议层的连接请求它往往只能看个IP没法深入判断同一个账号连续试密码是不是攻击行为。也就是说暴力破解在最底层其实是一个身份认证失败的过程。攻击者不关心你的业务逻辑他只关心连接、试密码、断开、再连接循环往复。这种请求和正常业务用户偶尔输错密码在形态上高度相似传统网络层设备根本没法区分。所以防暴力破解必须落到数据库引擎自身在认证这一层做拦截这正是Connection Control存在的理由。1.2 Connection Control做了两件事Connection Control是MySQL 5.7.17版本开始内置的一个插件族它分两部分connection_control主插件负责记录每个账号的连续失败尝试次数并触发延迟惩罚。connection_control_failed_login_attempts辅助插件把失败信息写入information_schema和performance_schema的对应表中方便DBA观察和分析。它的工作逻辑非常朴素正常用户输错密码输个三次五次就该停手了。如果一个账号在两分钟内连续失败20次那就不是手误而是有自动化脚本在跑。插件一旦判定这像暴力破解就会让下一次连接请求在数据库层强制等待一段时间等待时间会随着连续失败的次数递增。这个思路说白了就是给暴力破解增加时间成本。脚本跑一轮要等几秒甚至几分钟破解速度瞬间从每秒几十次降到几十次每小时攻击者自己就会失去耐心。而正常用户的体验几乎无感因为阈值没触发之前系统不做任何额外处理。1.3 核心参数只认三个Connection Control虽然拆成两个插件模块但真正需要DBA手动调的参数只有三个connection_control_failed_connections_threshold失败多少次后开始触发延迟默认值是3即连续失败3次第4次开始被惩罚。这个值可以设为0设为0表示不开启惩罚功能。connection_control_min_connection_delay单次最小延迟毫秒数默认1000毫秒也就是1秒。第一次触发惩罚时至少等这么久。connection_control_max_connection_delay单次最大延迟毫秒数默认2147483647毫秒约等于24.8天。设置上限是防止延迟无限扩大把账号彻底焊死。我个人的理解是这三个参数组合起来就是一个越挫越勇的保安刚开始只是让你进门等10秒如果你还继续在门口试密码下次就等20秒、40秒直到你受不了走人。2. 从零到一安装、启用与参数计算2.1 安装插件注意别漏了第二个MySQL 8.0和5.7.17以上的版本中插件文件已经随发行版自带不需要额外下载。安装有两种方式。第一种直接使用SQL语句动态安装INSTALL PLUGIN connection_control SONAME connection_control.so; INSTALL PLUGIN connection_control_failed_login_attempts SONAME connection_control_failed_login_attempts.so;注意我见过很多教程只装第一个结果后来查询失败记录时发现information_schema.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS表不存在才跑回来补装第二个。这两个插件建议始终成对安装因为主插件负责拦截辅助插件负责给你提供证据。安装后可以确认一下SELECT PLUGIN_NAME, PLUGIN_STATUS, PLUGIN_TYPE FROM information_schema.PLUGINS WHERE PLUGIN_NAME LIKE connection%;第二种方式在my.cnf配置文件中写死让MySQL启动时就加载[mysqld] plugin-load-add connection_control.so plugin-load-add connection_control_failed_login_attempts.so用配置文件方式的好处是实例重启后插件依然自动加载避免手动安装后机器重启、插件状态丢失的坑。坏处是修改配置需要重启MySQL所以我的习惯是测试环境用INSTALL PLUGIN生产环境直接改配置。2.2 参数计算从目标延迟倒推阈值很多人直接把三个默认参数装上就不管了这其实是偷懒。默认值3次失败即触发、最小延迟1秒对面向公网的实例来说这个灵敏度偏高会误伤偶尔输错密码的正常用户对纯内网的核心库来说3次又太松攻击者可以快速试几百个密码。所以参数设置要结合业务场景计算。先说公式。假设你设置的失败阈值为N最小延迟为T_min那么第N次失败后下一次连接也就是第N1次开始被延迟延迟时间至少为T_min。如果继续失败延迟时间会逐渐递增递增的规律是每次增加T_min直到达到最大延迟T_max。实际观测中MySQL官方文档并没有规定严格的递增公式社区普遍观察到的行为是延迟从T_min开始每多一次失败延迟增加约T_min直到封顶T_max。举个例子阈值10、最小延迟1秒那么第11次连接等待约1秒第12次约2秒第13次约3秒……直到达到最大延迟上限。如果你希望攻击者在触发惩罚后平均每轮只能尝试约6次密码即平均每次延迟约10秒那么可以这样推算让第N1次连接的延迟达到10秒附近即T_min10秒而这是第一次触发的等待时间之后每隔10次左右就翻倍。实际生产环境中不太需要精确计算到单次更实用的做法是明确业务诉求你能接受业务账号输错几次密码后被锁我把这个诉求翻译成参数业务场景失败阈值最小延迟秒最大延迟秒设计意图纯内网、低频业务系统55600误伤率低内网攻击源有限惩罚足够面向公网、有APP用户直接连库1013600留足手误空间惩罚缓慢拉长高安全等级、核心交易库31086400快速惩罚威胁者基本被劝退2.3 在线调整参数并持久化在MySQL 8.0中这三个参数都是动态变量不需要重启就能改。但在线改完之后要注意动态改的参数重启后会恢复默认值。如果你希望永久生效需要把参数写入my.cnf。在线修改命令如下SET GLOBAL connection_control_failed_connections_threshold 8; SET GLOBAL connection_control_min_connection_delay 2000; SET GLOBAL connection_control_max_connection_delay 3600000;确认修改生效SHOW VARIABLES LIKE connection_control%;然后到my.cnf中同步写入对应的配置段保证重启后参数不会回退。一个要注意的坑这三个参数如果你在命令行只改了GLOBAL级别当前已有的连接不会受影响新建立的连接才会按照新规则执行。对于高并发的业务这种渐变生效其实反而是优点不会出现瞬间把所有连接掐断的情况。当然如果你希望立即生效可以再执行SET PERSISTMySQL 8.0支持让它同时写入mysqld-auto.cnfSET PERSIST connection_control_failed_connections_threshold 8;3. 从参数到实战连接失败延迟的底层机制3.1 失败次数到底怎么计数的我们实际压测的时候发现一个很有意思的细节Connection Control的失败计数是按账号客户端主机两个维度组合来统计的不是单纯按用户名来算。什么意思同一个业务账号app_user从A机器连续输错密码和从B机器连续输错密码这两个会被视为两条独立记录。这一点在information_schema.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS表里体现得非常明显每一行都有一个USERHOST字段存的是类似app_user192.168.1.10这样的组合。这个设计其实很聪明。攻击者拿到了一堆弱口令账号他用同一台肉鸡来试那么同一USERHOST下的失败次数会快速累加。而正常用户如果从家里和公司两个IP轮流登即便都输错过密码影响也被隔离了不会因为总失败次数超标而被误伤。3.2 延迟产生的过程还原为了把机制讲透我实际搭了一个测试环境模拟。假设我设置了阈值3、最小延迟5秒、最大延迟60秒。现在模拟连续失败第1次失败不延迟正常返回Access denied。第2次失败不延迟正常返回Access denied。第3次失败不延迟正常返回Access denied。第4次失败连接请求进入等待队列5秒后才返回Access denied。第5次失败继续递增约10秒后返回Access denied。第6次失败约15秒后返回Access denied。到了第7次、第8次连接等待时间已经接近30秒此时客户端大概率已经超时。从攻击者的视角来看脚本的效率急剧下降跑了十几分钟可能也试不出几十个密码。从正常用户视角来看输错三次密码后再试时要等5秒这个体验虽然有点卡但对于防止账号被盗来说是完全可以接受的。3.3 成功登录后计数会重置有一个问题很多人没搞明白如果我输错了两次密码第三次输对了那这个失败2次的记录会保留多久会不会下次我再输错一次就直接触发延迟答案是不会。一旦该账号在该USERHOST上成功登录失败计数器立即清零。这是Connection Control非常人性化的设计。它惩罚的不是曾经输错过密码的人而是一直在输错密码的人。所以正常用户完全不用担心自己某天手滑多按了几次错误密码就被这个插件盯上好几个小时。当然如果你在同一个USERHOST上连续失败到触发延迟中途放弃了没有成功登录那么计数器会继续保留。保留多久取决于MySQL实例的运行时长和计数刷新策略在实践中可以理解为持续累积直到出现一次成功登录或者达到某个内部清理条件。4. 生产环境高频问题与排查实录4.1 插件装上了却不生效这是问得最多的问题。很多DBA装完插件后测试了一下连续输错密码发现数据库还是秒回Access denied一点延迟都没有。查了一圈发现是参数connection_control_failed_connections_threshold被设成了0。MySQL官方文档里写明该参数设置为0时表示关闭惩罚功能。装完插件后默认值虽然是3但如果你的实例之前设置过--connection-control-failed-connections-threshold0或者你在my.cnf里写死过这个值插件即使加载了也不会产生实际效果。排查方法很简单SHOW VARIABLES LIKE connection_control_failed_connections_threshold;如果看到0改回非0值就行。这种事靠看日志看不出来因为插件根本没报错它只是不干活。4.2 参数改了延迟时间还是不对有同行跟我反馈说设置了最小延迟为10秒但实际观察下来第4次连接只等了2秒百思不得其解。这里要再强调一次最小延迟是下限不是固定值。当失败次数超过阈值后延迟从最小值开始递增。也就是说设置min_connection_delay10000只代表第一次触发惩罚时的等待时间至少为10秒但如果你的连续失败刚好卡在阈值刚被突破的位置MySQL内部会结合其他因素给出一个不小于10秒的等待值。实践中这个值会非常接近10秒但不会少于它。如果你观察到的延迟时间远小于设定的最小值那大概率是阈值判定还没触发或者参数实际没有生效。另外注意min的单位是毫秒很多人习惯性地填10以为是10秒实际才10毫秒等于无效配置。生产环境建议至少填1000以上。4.3 失败计数表的记录为什么是空的安装完辅助插件后去查information_schema.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS发现空表。这不是故障而是正常现象。这张表只在失败次数达到阈值后才会写入数据。如果失败次数没到阈值MySQL认为这是正常波动不记录。所以如果你想测试这个插件的效果需要连续失败超过阈值次数再去查这张表。例如阈值是3那你至少要试4次错误密码第4次开始触发延迟这个时候表里才会有一条记录。4.4 误伤业务账号高并发场景下的连接池堆积这个坑很隐蔽但一旦踩到就是事故级别的。假设你有一个Java应用连接池配置了20个连接。正常情况下这20个连接是反复复用的不会频繁建立新连接。但如果应用端的数据库密码被改错了或者连接池里的某个连接因为网络抖动断开了应用会尝试重新连接此时如果集中重试就会立刻触发Connection Control的延迟惩罚。更麻烦的是连接池在等待新连接时不会把等待算作业务超时而是会堆积请求。当过了一段时间惩罚延迟过去后应用才能继续连库。但如果应用频繁重试惩罚会不断累积。一旦到了这个状态换对密码都救不了你因为新连接会被延迟挡住。在我们自己的一次压测事故中就是因为运维改了数据库密码但应用配置没同步更新所有连接开始疯狂失败重连最终触发Connection Control的最大延迟惩罚导致该账号在一定时间内彻底无法建立新连接。解决方法是临时调大阈值或者临时关闭惩罚等应用配置改对后再恢复。这个教训说明参数调优一定要考虑监控告警和紧急降级方案。4.5 与账号自动锁定的关系常见的误解是Connection Control会把账号锁定。其实它不会真正锁定账号它只是让连接请求等待更长的时间。账号本身依然是可用状态只是每次尝试都会被拖慢。如果你的需求是失败次数达到N次就锁账号那是另一个功能——CREATE USER语句里的FAILED_LOGIN_ATTEMPTS和PASSWORD_LOCK_TIME选项或者是手动锁定账号。我个人建议把两者结合短平快的暴力破解靠Connection Control拖时间长期撞库靠账号锁定来兜底。但账号锁定的阈值要设置得保守一点因为一旦锁错那就是真的锁死了业务直接瘫痪。5. 配套监控与周边防御的整体协同5.1 如何从日志中识别主动攻击行为Connection Control产生的延迟在MySQL错误日志中不会主动打印除非你设置了额外的log_error_verbosity。因此日常监控更多依赖MySQL提供的状态变量SHOW STATUS LIKE Connection_control_delay_generated;这个变量统计了插件累计产生延迟的次数。如果这个数字增长速度很快说明频繁有连接触发惩罚要么是攻击要么是配置错误。建议接入监控系统设置告警阈值比如5分钟内增长超过100次就触发告警。更细粒度的方法是通过performance_schema来查看连接失败的具体来源。可惜的是MySQL本身不直接记录每个失败连接的来源IP这条链路更多依赖网络层日志或审计插件。我的建议是如果有合规需求直接上MySQL Enterprise Audit否则开启通用日志可以选择性记录登录失败事件但生产环境不建议全量开启对性能影响太大。5.2 Connection Control与fail2ban的协同分工很多文章把Connection Control和fail2ban放在对立面比较其实两者解决的问题层级完全不同。fail2ban工作在操作系统层它盯的是系统认证日志发现某个IP大量触发SSH密码错误就直接在iptables层面封掉这个IP。Connection Control工作在MySQL层它只关心MySQL内部的登录失败。组合使用的价值在于fail2ban解决了同一IP扫全库所有账号的问题Connection Control解决了多个IP轮流试同一个账号的问题。单靠fail2ban攻击者换一批IP还是能继续试单靠Connection Control攻击者用大量IP同时试也能绕过单账号延迟。两者结合后就形成了纵深防御外部IP被系统层封禁内部账号被数据库层拖慢。5.3 一套可以抄作业的部署模板以下是一套我用于生产环境MySQL 8.0的模板化配置供你参考[mysqld] # Connection Control plugin-load-add connection_control.so plugin-load-add connection_control_failed_login_attempts.so connection_control_failed_connections_threshold 8 connection_control_min_connection_delay 2000 connection_control_max_connection_delay 3600000配合账号侧策略ALTER USER app_user% FAILED_LOGIN_ATTEMPTS 10 PASSWORD_LOCK_TIME 1;意思是连续失败10次后账号锁1天。对核心业务账号阈值可以收紧到5次左右。最后说一个日常运维的小习惯每季度做一次账号权限复查时把information_schema.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS导出来看一眼里面往往藏着很多正常情况下根本发现不了的扫描痕迹。比如某个从没见过的USERHOST出现了多次失败记录那基本可以断定有人拿着破解好的字典在你的库上试。这时候不只是改密码的问题还得溯源一下这个IP怎么进来的。插件给的是缓冲时间真正的安全还得靠人的警惕性。