数据库是绝大多数业务的”金库”——应用被攻破后,攻击者最终目标几乎都是拖库。而很多 MySQL 实例的默认配置对内部人、对已经进内网的攻击者相当友好:root 能远程登录、有匿名用户、测试库没清、还能用 into outfile 直接写文件。
这篇把 MySQL 加固最该做的几项讲清,原则仍是”最小权限 + 最小暴露”。
所有操作仅用于你自有或已授权的数据库。改配置、删账户前务必备份,生产库操作避开业务高峰。
第一关:保护 root 账户
默认安装的 MySQL 有时 root 无密码或弱密码,且允许远程登录,这是最危险的组合。
-- 给 root 设强密码(8.0 用 ALTER USER)
ALTER USER 'root'@'localhost' IDENTIFIED BY '复杂密码不低于12位';
-- 禁止 root 远程登录:root 只允许本机
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost','127.0.0.1');
FLUSH PRIVILEGES;
关键原则:root 只从 localhost 连,应用用单独的低权限账号连库。绝不要让 root 从应用服务器或公网直连。
第二关:删除匿名用户与测试库
MySQL 默认可能带匿名用户(空用户名,任何密码都能登)和 test 库(任何人有完全权限),必须清掉:
-- 删除匿名用户(User 为空)
DELETE FROM mysql.user WHERE User='';
-- 删除 test 库
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
匿名用户是”隐形的门”——你以为没账号进不来,其实空用户名就能登。清掉它。同时确认 mysql.user 表里没有多余账户。
第三关:应用账号最小权限
应用连接数据库,绝不该用 root,也不该给 ALL PRIVILEGES。按库按权限授权:
-- 建一个只属于某库、只有增删改查的应用账号
CREATE USER 'appuser'@'10.0.0.%' IDENTIFIED BY '强密码';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'appuser'@'10.0.0.%';
FLUSH PRIVILEGES;
-- 切忌:GRANT ALL ON *.* TO appuser@'%';
要点:
- 账号绑定来源 IP/网段(
'appuser'@'10.0.0.%'),而不是@'%'任意主机。 - 只给业务真正需要的权限,不要用
ALL、不要用*.*全库。 - 不同业务用不同账号,一个泄露不影响全部。
第四关:网络暴露最小化
MySQL 默认监听 3306,很多运维图省事直接对外。正确做法:
- 在
my.cnf里设bind-address = 127.0.0.1,只允许本机连;应用和库同机或走内网。 - 若必须远程,用防火墙/安全组只允许应用服务器 IP 访问 3306,绝不公网开放。
- 改默认端口
port = 3307只能降扫描命中率,不是根本安全,别依赖。
[mysqld]
bind-address = 127.0.0.1
port = 3306
第五关:关键安全配置项
my.cnf 里有几个和”被利用写文件/读文件”直接相关的开关,必须重视:
[mysqld]
# 禁止从客户端加载本地文件,防 LOAD DATA LOCAL 注入与数据外泄
local_infile = 0
# 限制 INTO OUTFILE / LOAD_FILE 只能读写这个目录,设为 NULL 则完全禁止
secure_file_priv = /var/lib/mysql-files/
# 不读取用户家目录下的配置文件,防提权
secure-auth = 1
# 跳过符号链接,防利用 symlink 访问任意文件
symbolic-links = 0
secure_file_priv 特别关键:很多”MySQL 写 Webshell”的利用,前提就是 secure_file_priv 为空(允许写任意目录)。设为特定目录能直接堵死这类利用。
第六关:日志与审计
开启日志便于事后排查异常查询和拖库行为:
[mysqld]
# 通用查询日志(调试用,生产慎用,量大)
general_log = 0
# 慢查询日志(可发现全表扫拖库)
slow_query_log = 1
long_query_time = 2
# 二进制日志(主从/恢复用,也是审计线索)
log-bin = mysql-bin
企业版有 MySQL Enterprise Audit,社区版可借助 init-connect + 审计表、或第三方插件(如 MariaDB Audit Plugin、Percona 的 audit_log)记录连接与语句。重点是:异常的大量 SELECT *、非业务时段的导出,能在日志里留下痕迹。
常见误区
- 应用用 root 连库:一旦应用被注入,攻击者直接拿到数据库最高权限,等于没防。
@'%'任意主机授权:账号可从任意 IP 连,配合弱口令就是公开入口。secure_file_priv留空:给”写文件/读文件”类利用大开方便之门。- 开了 general_log 又不清理:日志暴涨把磁盘写满,反而造成业务故障。
- 只加固不备份:加固防外贼,备份防内灾(误删、勒索),两者都不可少。
进阶:用脚本核查配置
# 检查关键变量是否按基线设置
mysql -N -e "SHOW VARIABLES LIKE 'secure_file_priv';"
mysql -N -e "SHOW VARIABLES LIKE 'local_infile';"
mysql -N -e "SELECT user,host FROM mysql.user WHERE user='';" # 应为空
把这几条做成定时任务,输出偏离基线的项,就是最简单的”数据库基线核查”。
自测题
- 为什么 root 账户必须禁止远程登录?
- 匿名用户为什么危险?怎么清?
secure_file_priv设为NULL或特定目录,能防住哪类利用?- 应用账号授权为什么要用
'appuser'@'10.0.0.%'而不是@'%'?
实战要点与深度解析
MySQL 加固里最典型的”配置对了但依然危险”场景,是应用账号虽然低权限,却能从应用服务器直连数据库且密码写在了代码里。很多代码仓库(甚至公开到 GitHub 的)里硬编码着数据库密码,一旦仓库泄露,攻击者拿着密码从任意能连通 3306 的地方就能登进来。所以”账号绑定来源 IP”这一条格外重要:即使密码泄露,只要攻击者不在白名单网段,依然连不进来。更进一步,应把凭证放进配置中心/密钥管理服务,而不是明文写在代码或配置文件里。
再讲一个进阶点:performance_schema 与审计的取舍。社区版 MySQL 没有企业级审计插件,但你可以利用 init-connect 参数:让每个连接先执行一条 INSERT 把”谁、从哪 IP、什么时间连的”写进一张审计表。虽然不如专业审计插件强大,但能以极低成本拿到”连接溯源”能力,配合慢查询日志,足以发现”非业务时段的大量连接""异常来源 IP”等信号。当然,这张审计表本身也要保护好,防止被攻击者清空。
关于 主从复制与加固的关系:很多生产库是主从架构,从库用于读写分离或备份。这里有个隐患:从库往往被配成”信任主库、权限较松”,且可能对外开放供报表系统读取。如果从库安全水位低,攻击者拿下从库后能读到全量数据,危害不亚于主库。所以主从都要按同一基线加固,尤其从库也不能用弱口令、也不能随意暴露。
还有一个容易被忽略的配置:skip-grant-tables 绝不能留在生产配置里。这个参数会让 MySQL 跳过所有权限验证,任何人都能以管理员身份登入——它是”忘密码时的急救开关”,若误留在配置中,等于给数据库开了无锁后门。每次排障用完必须立即移除并重启。
最后提醒:数据库加固和备份是双胞胎。前面所有账户、权限、网络加固,防的是”外部/内部人员非法进入”;但如果是勒索软件加密了数据文件、或误操作 DROP 了表,加固再多也救不回来。所以 mysqldump/物理备份 + 异地/离线保留 + 定期恢复演练,必须和加固同步做。很多单位”库很安全但没备份”,一次误删就酿成事故,这个教训值得反复强调。
速查清单与排错口诀
MySQL 加固口诀:root 本、匿测清、权最小、源要限、文件锁、志要记、备同行。即 root 只本机、清匿名与测试库、权限最小且限来源、锁文件读写、开日志、备份与加固同行。
排错场景:设了 bind-address=127.0.0.1 后应用连不上库。这通常是应用和库不在同一台、却忘了把应用服务器 IP 加进账号的授权主机(如 'appuser'@'10.0.0.%'),或防火墙没放通。正确做法是”库本机监听 + 应用账号限定来源网段 + 防火墙仅放行该网段”,三层一致才通。另一个坑:把 secure_file_priv 设成目录后,某合法导出功能(如报表生成)失效——这时应把导出路径改到该允许的目录,而不是退回”允许任意目录”。加固和业务的冲突,永远用”换路径/换方式”化解,而不是撤掉安全配置。
进阶速记与误区辨析
MySQL 加固里也有几组容易让人掉坑的概念,专门辨析一下。
第一组,root 禁远程与运维便利性。禁用 root 远程登录确实更安全,但很多运维为了方便,又偷偷建了一个权限几乎等同 root 且允许任意来源连接的账户,结果只是把风险换了个名字。真正的做法是应用用受限账户、来源限定网段、权限只给业务所需,不能明面上禁了 root 暗地里又开了个超级账户。
第二组,强密码与凭证保管。设了强密码只是第一层,如果密码被明文写在代码仓库或者配置文件里,仓库一泄露密码就跟着泄露,强密码形同虚设。凭证应该放进配置中心或者密钥管理服务,代码里只留引用,不能把密码当普通字符串到处写。
第三组,限制来源与网络暴露。给应用账户限定了来源网段,但如果数据库本身还监听在公开地址且防火墙没拦,攻击者依然可能从别的路径摸到。所以限制来源要和应用侧的网络隔离、防火墙规则配合起来,三层一致才真正堵住入口。
第四组,安全参数与正常功能。把文件读写参数限制到指定目录能挡住很多写文件类利用,但某些合法的导出功能可能正依赖更宽的路径。这时不能简单退回”允许任意目录”,而应该把导出功能改到允许的目录里去,用改路径而不是撤安全来化解冲突。
速记口诀收尾:root 本机、匿名清掉、权限最小、来源要限、文件锁死、日志要记、备份同行。七句话把 MySQL 加固的主线串起来,配合前面的实战章节,基本能覆盖日常绝大多数场景。
这一篇你该记住的
- root 只从 localhost 连,设强密码,绝不让远程直连。
- 删除匿名用户与 test 库,清掉”隐形门”。
- 应用账号最小权限、绑定来源 IP、禁止
ALL ON *.*。 bind-address=127.0.0.1+ 防火墙仅放行应用 IP,3306 不暴露公网。local_infile=0、secure_file_priv限制目录,堵死写/读文件利用。- 开慢查询/二进制日志做审计线索,且加固同时务必做备份。
下一篇我们看 Web 服务器 Nginx 的安全加固。