MySQL数据库默认安装后存在大量安全风险,包括空口令、匿名用户、远程任意主机登录、弱权限配置以及未加密的流量传输。这些问题在公网环境或等保测评场景下会被直接判定为高危漏洞。加固不是装个防火墙就完事,而是要从安装后第一分钟开始,逐项收紧账号、权限、日志、网络和文件系统层面的配置。下面按实际运维顺序给出可直接执行的加固方案。

1. 安装后的初始清理:删除匿名用户、测试库和空口令账号

MySQL 5.x和8.0在默认初始化后,通常会存在匿名用户(''@'localhost'和''@'hostname')以及test数据库。这些对象没有任何业务用途,却可能被用来提权或探测数据库结构。先执行以下SQL清理:

DELETE FROM mysql.user WHERE User='';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;

清理后必须执行FLUSH PRIVILEGES让权限表立即生效。同时检查root账号是否允许从非localhost主机登录。很多云数据库或一键安装脚本会生成root@'%',这等于把最高权限暴露给所有网络接口。执行以下SQL查看:

SELECT user, host, plugin FROM mysql.user;

如果存在root@'%',要么删除,要么改成只允许内网管理网段访问,例如root@'192.168.10.%'。生产环境建议为root保留唯一的localhost登录通道,其他管理员使用独立账号并限制来源IP。

2. 密码策略与认证插件强制启用

MySQL 8.0默认使用caching_sha2_password插件,安全性比mysql_native_password高,但部分老客户端不兼容。不要为了兼容性回退到native插件,而应升级客户端。同时开启密码复杂度校验组件:

INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 14;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;

STRONG策略要求密码包含大小写字母、数字和特殊字符,长度至少14位。对于已有账号,可以用以下SQL找出弱密码账号并强制过期:

SELECT user, host FROM mysql.user WHERE LENGTH(authentication_string) < 40;
ALTER USER 'username'@'host' PASSWORD EXPIRE;

认证插件方面,确保所有账号都使用caching_sha2_password。如果确实存在老旧应用无法支持,可以单独为那个账号设置native插件,但绝不要全局降级。

3. 权限最小化:禁用SUPER、FILE、PROCESS等危险权限

很多开发为了方便,直接给应用账号GRANT ALL PRIVILEGES,这等于把数据库管理权交给了应用程序。一旦应用出现SQL注入,攻击者可以直接读写服务器文件、查看所有进程甚至关闭数据库。应用账号只应拥有对业务库的SELECT、INSERT、UPDATE、DELETE权限,以及必要的EXECUTE权限。禁止授予SUPER、FILE、PROCESS、SHUTDOWN、RELOAD、CREATE USER等管理权限。

对于需要执行存储过程或触发器的账号,单独授予EXECUTE和TRIGGER权限,不要用ALL代替。定期审计权限分配:

SELECT user, host, db, Select_priv, Insert_priv, Update_priv, Delete_priv, 
       File_priv, Super_priv, Process_priv 
FROM mysql.db 
JOIN mysql.user USING (user, host) 
WHERE File_priv='Y' OR Super_priv='Y' OR Process_priv='Y';

这条SQL能快速找出拥有危险权限的账号。发现后立即REVOKE,并检查应用是否因此报错。如果报错,说明应用设计本身越权,应修改代码而不是恢复权限。

4. 网络层加固:强制SSL/TLS加密连接

MySQL默认以明文传输SQL语句和查询结果,在跨机房或公网传输时,任何中间节点都可以抓包看到完整数据。开启SSL需要先准备证书,MySQL自带mysql_ssl_rsa_setup工具可以快速生成自签名证书:

mysql_ssl_rsa_setup --datadir=/var/lib/mysql
chown mysql:mysql /var/lib/mysql/*.pem

然后在配置文件中启用:

[mysqld]
require_secure_transport = ON
ssl_ca = /var/lib/mysql/ca.pem
ssl_cert = /var/lib/mysql/server-cert.pem
ssl_key = /var/lib/mysql/server-key.pem

重启后,所有非SSL连接都会被拒绝。对于需要远程管理的账号,创建时指定REQUIRE SSL:

CREATE USER 'dba_remote'@'203.0.113.50' IDENTIFIED BY 'StrongPass!2024' REQUIRE SSL;
GRANT ALL PRIVILEGES ON *.* TO 'dba_remote'@'203.0.113.50' WITH GRANT OPTION;

注意,开启require_secure_transport后,本地socket连接不受影响,但所有TCP连接必须走SSL。如果应用不支持SSL,需要升级驱动或使用SSH隧道代替明文TCP。

5. 操作系统与文件系统层面的权限控制

MySQL的数据目录、配置文件和日志文件权限经常被忽视。默认安装后,/etc/my.cnf可能全局可读,里面包含明文密码或密钥路径。执行以下命令收紧权限:

chmod 600 /etc/my.cnf
chown mysql:mysql /etc/my.cnf
chmod 750 /var/lib/mysql
chmod 640 /var/lib/mysql/*.pem
chmod 600 /var/log/mysql/*.log

数据目录内的文件应全部属于mysql用户,且不允许其他系统用户读取。如果使用了LOAD DATA INFILE或SELECT INTO OUTFILE,必须设置secure_file_priv参数,限制文件读写目录:

[mysqld]
secure_file_priv = /var/lib/mysql-files

设置为空字符串表示禁用文件读写功能,设置为具体路径则只允许在该目录下操作。不要设置为空值(NULL),那会完全禁用,但有些备份工具依赖该功能,需要根据实际业务调整。

6. 日志审计:开启通用查询日志、错误日志和慢查询日志

安全事件发生后,如果没有日志,就无法追溯攻击路径和影响范围。MySQL默认只开启错误日志,且内容有限。建议在配置文件中增加:

[mysqld]
log_error = /var/log/mysql/error.log
general_log = ON
general_log_file = /var/log/mysql/general.log
log_slow_extra = ON
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

通用查询日志会记录所有SQL语句,包括登录尝试和权限变更操作。在高并发场景下,general_log会消耗大量磁盘IO,可以改为只记录管理类操作,或者使用审计插件。但至少在等保测评期间,应保持开启状态。同时设置日志轮转,防止磁盘写满:

# /etc/logrotate.d/mysql
/var/log/mysql/*.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    create 600 mysql mysql
    sharedscripts
    postrotate
        mysqladmin flush-logs
    endscript
}

日志文件本身也要防止被非授权用户读取,权限设为600,并且定期备份到独立的日志服务器。

7. 定期更新与漏洞响应

MySQL每个季度都会发布安全更新,修复包括权限提升、缓冲区溢出、拒绝服务等漏洞。很多生产环境还在运行5.6或5.7的旧版本,这些版本已经停止官方支持,漏洞不会被修复。升级到8.0是唯一长期可行的方案。升级前先在测试环境验证应用兼容性,重点检查认证插件、SQL语法变化和字符集差异。

对于无法立即升级的系统,至少要做到:禁用远程root登录、限制所有账号来源IP、开启SSL、定期审计权限表。同时关注MySQL官方安全公告,对已公开的CVE进行评估,确认是否影响当前版本。漏洞响应不是一次性的,而是持续的过程。

8. 备份与恢复策略中的安全考量

备份文件是数据库安全中最容易被忽略的一环。mysqldump导出的SQL文件包含完整数据和结构,如果备份目录权限不当,等于把整个数据库拱手送人。备份文件应加密存储:

mysqldump --single-transaction --routines --triggers dbname | openssl enc -aes-256-cbc -salt -out backup.sql.enc

解密时使用:

openssl enc -d -aes-256-cbc -in backup.sql.enc -out backup.sql

备份文件不要放在数据库服务器本机,应传输到独立的备份服务器或对象存储,并设置生命周期策略。定期做恢复演练,验证备份可用性。很多企业直到数据丢失才发现备份文件早已损坏或密码丢失,这种教训代价极大。

9. 使用MySQL Enterprise Firewall或第三方审计插件

对于高安全要求的环境,可以考虑部署MySQL Enterprise Firewall,它能学习应用的SQL模式,并阻止异常SQL语句。例如应用正常只执行特定的SELECT和INSERT语句,突然出现一条DROP TABLE或SELECT * FROM mysql.user,防火墙可以直接拦截。开源替代方案包括Percona Audit Plugin和MariaDB Audit Plugin,它们能记录详细的连接和查询事件,并输出到syslog或独立日志文件。

审计插件部署后,要定期分析日志,重点关注:登录失败、权限变更、DDL操作、访问mysql系统库的查询。这些行为往往是攻击的前兆。配合SIEM系统可以做到实时告警,但至少每周人工检查一次审计日志。

10. 配置检查清单与自动化验证

加固完成后,用以下SQL和命令做一次全面检查。首先检查匿名用户和空密码:

SELECT user, host FROM mysql.user WHERE user='' OR authentication_string='';

检查远程root:

SELECT user, host FROM mysql.user WHERE user='root' AND host NOT IN ('localhost', '127.0.0.1', '::1');

检查SSL状态:

SHOW VARIABLES LIKE 'have_ssl';
SHOW VARIABLES LIKE 'require_secure_transport';

检查文件读写限制:

SHOW VARIABLES LIKE 'secure_file_priv';

检查日志开启状态:

SHOW VARIABLES LIKE 'general_log';
SHOW VARIABLES LIKE 'slow_query_log';

这些检查可以写成脚本,定期执行并输出报告。对于多实例环境,用Ansible或Puppet统一推送配置,避免手工操作遗漏。安全加固不是一次性的任务,而是需要纳入日常运维流程的持续动作。