MySQL装完并不是终点,反而是一个暴露面开始形成的时间点。默认安装通常会保留匿名用户、允许root从任意主机连接、存在test数据库,并且监听所有网卡。如果这台服务器直接接入了公网或公司内网,这些默认设置就相当于给外部扫描提供了现成入口。所以安装之后的第一件事不是建业务库,而是先完成安全策略配置。

一、先做基础清理:用好mysql_secure_installation
MySQL官方提供了一条交互式命令 mysql_secure_installation,专门用来处理安装后的常见风险。执行后它会引导你完成若干步骤:设置root密码、移除匿名用户、禁止root远程登录、删除test数据库并刷新权限表。每个步骤都有明确的yes或no提示,新手也能按窗口提示完成。对刚安装好的实例来说,这是成本最低、收益最高的一步。
不过该脚本并不是万能的。它只能处理默认账户和默认库,并不会限制后续新建用户的来源IP,也不会启用密码复杂度校验。如果业务需要多个账号,要在清理完默认项后继续配置全局密码策略和账号体系。还有一点容易被忽视:在MySQL 8.0里,root默认使用caching_sha2_password认证插件,某些老客户端可能连不上,需要按需调整认证插件,而不是为了方便直接改回弱口令。
# 执行安全初始化向导 mysql_secure_installation
如果不想交互执行,也可以登录后手动清理。比如查看当前用户列表并删除空用户:
SELECT user, host, plugin FROM mysql.user; DELETE FROM mysql.user WHERE user = ''; FLUSH PRIVILEGES;
二、用validate_password强制密码复杂度
密码是所有安全策略里最直接的一层。MySQL从8.0开始提供 validate_password 组件,可以强制新密码满足长度、大小写、数字、特殊字符等要求。先确认是否已安装:
SELECT * FROM mysql.component;
如果没有结果,可以用 INSTALL COMPONENT 加载。然后设置策略级别。常见级别有LOW、MEDIUM、STRONG,分别对应不同校验维度。STRONG级别会检查密码字典文件,适合对安全要求较高的场景。配置项可以直接用 SET GLOBAL 修改。
INSTALL COMPONENT 'file://component_validate_password'; SET GLOBAL validate_password.policy = STRONG; SET GLOBAL validate_password.length = 12; SET GLOBAL validate_password.mixed_case_count = 1; SET GLOBAL validate_password.number_count = 1; SET GLOBAL validate_password.special_char_count = 1;
需要注意的是,这些全局参数在重启后可能失效,应该写入配置文件 my.cnf 或 mysqld.cnf 中的 [mysqld] 段。MySQL 8.0的组件参数写法带点号,而旧版插件参数写法是下划线,例如 validate_password_policy。如果发现参数不生效,先检查版本和参数名是否匹配。
另外,密码策略要配合账号密码过期机制一起使用。可以通过 ALTER USER ... PASSWORD EXPIRE INTERVAL 90 DAY 让密码定期轮换,避免一个密码用几年。定期轮换会增加一定的运维成本,但相比密码泄露后无人知晓,这种代价可以接受。
三、停止使用root直连,建立最小权限账号
日常开发或应用连接最好不要直接使用root。root权限过大,一旦被拖库,整个实例都可能被清空。正确做法是为每个业务单独创建账号,并只授予必要的库表权限。授权语句要精确到库、表,甚至字段。比如一个报表账号只读,就只给SELECT,不要图省事直接GRANT ALL。
CREATE USER 'report_user'@'192.168.10.%' IDENTIFIED BY 'R3p0rt!Str0ng'; GRANT SELECT ON business_db.* TO 'report_user'@'192.168.10.%'; FLUSH PRIVILEGES;
这里 '192.168.10.%' 限制了账号只能从指定网段登录,即使密码泄露,攻击者从公网也无法直接使用这个账号建立连接。生产环境建议把来源IP细化到具体应用服务器地址,而不是写成 '%'。如果业务部署在多个网段,可以创建多个同用户名但不同host的账号,MySQL会按host精确度匹配。
权限收回同样重要。开发环境临时开过的权限,到生产上线前应统一审计。用 SHOW GRANTS FOR 查看每个账号的有效权限,用 REVOKE 收回多余项。对于不再使用的账号,及时 DROP USER。账号越少,维护成本和暴露面越小。
SHOW GRANTS FOR 'report_user'@'192.168.10.%'; REVOKE DELETE ON business_db.* FROM 'report_user'@'192.168.10.%'; DROP USER 'old_user'@'%';
四、收紧网络监听与传输加密
数据库默认监听 0.0.0.0,所有网卡都能收到连接请求。如果只需要本机应用访问,就把 bind-address 设置为 127.0.0.1;如果需要跨服务器访问,至少也要在系统防火墙或安全组里限制来源IP,不要开放全员端口。修改配置文件后记得重启MySQL。
[mysqld] bind-address = 127.0.0.1 port = 3307 skip-name-resolve
端口也可以改,但端口修改不能替代防火墙规则。把默认3306改成3307,能减少一部分自动化扫描的命中,不过专业攻击者仍然可以通过端口扫描发现服务。真正有效的是限制源IP和启用加密连接。MySQL支持SSL/TLS,配置后可以要求指定账号必须通过SSL登录。
启用SSL前需要准备证书。可以用OpenSSL生成自签名证书,适合内网环境;生产环境最好使用内部CA签发。MySQL启动时配置证书路径:
[mysqld] ssl_ca = /etc/mysql/certs/ca.pem ssl_cert = /etc/mysql/certs/server-cert.pem ssl_key = /etc/mysql/certs/server-key.pem require_secure_transport = ON
设置 require_secure_transport = ON 后,除了本机socket连接,其他TCP连接都必须使用SSL。创建账号时用 REQUIRE SSL 可以强制指定账号加密,查看连接是否加密可以用 SHOW STATUS LIKE 'Ssl_cipher'。
CREATE USER 'secure_user'@'192.168.10.%' IDENTIFIED BY 'S3cure!P@ss'; GRANT SELECT ON business_db.* TO 'secure_user'@'192.168.10.%'; SHOW STATUS LIKE 'Ssl_cipher';
五、开启审计日志,保留排查线索
很多数据库事故在发生前都有异常登录或异常查询,但因为没有开日志,事后很难还原攻击路径。MySQL自带的通用查询日志和慢查询日志虽然会带来一定性能损耗,但在安全敏感环境中值得开启。通用日志记录所有连接和SQL,慢查询日志则能定位长时间运行的语句。两者都可以写到文件或表里,写表更方便用SQL筛选。
SET GLOBAL general_log = 'ON'; SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2; SET GLOBAL log_output = 'TABLE';
开启通用日志后,连接建立、断开和每条SQL都会被记录在 mysql.general_log 表里。可以按时间倒序查看最近的连接来源:
SELECT event_time, user_host, argument FROM mysql.general_log ORDER BY event_time DESC LIMIT 50;
如果业务并发较高,通用日志会迅速膨胀并增加I/O压力。更合理的做法是只在安全事件调查期间临时开启,日常使用企业级审计插件或外部日志采集系统。MySQL社区版没有原生审计插件,但可以通过Percona Server或MariaDB的审计功能弥补,或者将binlog作为辅助审计源。
除了SQL审计,错误日志也有安全含义。比如频繁的访问拒绝、连接协议错误都可能表明有人在暴力尝试。把错误日志接入集中日志平台并设置告警,可以在攻击早期发现异常。日志目录权限也要检查,确保只有mysql用户可写。

