ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL安全加固:mysql_secure_installation完全指南与踩坑实录

MySQL安全加固:mysql_secure_installation完全指南与踩坑实录 我们环境里有一台跑了好几年的 MySQL 5.7某次安全巡检时竟然发现mysql.user表里存在空密码的匿名账号还带着默认的 test 库。翻日志看不出是谁建的多半是当年装完数据库后直接进了业务上线流程跳过了mysql_secure_installation这一步。后来我在所有新环境的初始化清单里把这条命令列为“装完 MySQL 后必须先干的事”。这篇就把这条安全脚本的完整执行过程、每一步的实际动作、以及我在各种系统上踩过的坑一次说清楚。1. 为什么安装完 MySQL 后第一件事是跑安全脚本1.1 默认安装状态离安全基线有多远MySQL 安装完成、服务启动成功、SELECT 1能正常执行很多人就觉得数据库已经能用了。但如果你用的是发行版自带的安装包、二进制包或者源码编译方式初始状态下的安全问题其实非常集中root 账号密码为空或者只有一个临时密码存在匿名用户localhostroot 允许从任意主机远程连接自带 test 数据库以及 test_ 开头的通配授权记录这四条单独看都不起眼组合起来就是一套非常经典的数据库入侵路径扫到 3306 端口 - 用 root 空密码尝试登录 - 登录成功 - 拿到整个实例的控制权。就算 root 有密码如果允许root%远程登录攻击者还可以在公网上直接对这个账号做密码爆破连内网都不用进。我习惯把刚装完的 MySQL 比作一套新房墙砌好了、门窗都装了但钥匙摆在地垫下面后门也没锁。mysql_secure_installation就是那个帮你把地垫下的钥匙收走、把后门锁上的过程。1.2 脚本的五个检查项和对应风险mysql_secure_installation最核心的价值不是它做了什么高深操作而是它把所有“新装实例最容易犯的错”集中成了几个交互式问题。网上很多文章只告诉你“照着选 y 就行”但不讲每个 y 背后到底改了什么。我把五个检查项和对应风险整理成了下面这张表交互问题脚本实际动作消除的风险设置 root 密码更新 root 账号密码root 空密码 / 弱密码移除匿名用户删除空用户名的账号无账号直接登录禁止 root 远程登录删除 host 非本机的 root 账号root 账号在公网被爆破移除 test 数据库删除 test 库及通配授权记录任意登录用户可读写 test 库重新加载权限表执行 FLUSH PRIVILEGES让上述变更立即生效这个脚本适合在 MySQL 刚装好、还没有创建任何业务账号的时候运行。如果是已经跑了一段时间的生产库后面再执行就需要逐项评估了尤其是“禁止 root 远程登录”和“移除 test 数据库”这两项可能会影响现有应用的连接方式。1.3 它解决不了什么需要明确一点mysql_secure_installation只是初始化安全基线的工具它不负责创建业务账号不负责限制监听地址也不负责开启 SSL、审计日志、二进制日志这类更深层的安全能力。跑完它并不代表数据库就绝对安全了只能说把最常见、最容易利用的几个洞先堵上了。我更愿意把它定位成“MySQL 安装收尾动作”而不是“一键安全加固”。2. 交互式执行全过程拆解MySQL 5.7 和 8.0 在执行mysql_secure_installation时交互逻辑基本一致只在 validate_password 组件的启用方式和提示措辞上有少量差异。下面按实际的执行顺序逐步说明每一步的意义。2.1 第一步输入当前 root 密码而不是设置新密码脚本启动后第一个提示是Securing the MySQL server deployment. Enter current password for root (enter for none):这里特别容易踩坑。很多人第一次跑的时候以为这是“让我设置新密码”直接输入了一个自己想用的新密码结果回车后得到Access denied。这个提示的准确含义是输入当前 root 密码。如果你刚装好 MySQL 且从未设置过密码此处直接回车。脚本是通过 socket 连接本地 MySQL 实例的连接时会读取/etc/my.cnf等配置文件里的 socket 路径。如果脚本找不到正确的 socket 文件可能会看到ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。遇到这个报错先确认 mysqld 进程是否启动、socket 路径是否和my.cnf里一致再跑一次。2.2 validate_password 组件密码复杂度策略装还是不装输入正确密码后脚本会问Install VALIDATE PASSWORD component?这个组件的作用是建立密码强度校验机制。之后无论是通过 CREATE USER 创建新用户还是 ALTER USER 修改密码MySQL 都会按设定的策略检查密码是否符合强度要求。选择安装后会继续问密码验证策略级别策略级别校验内容适合场景0 / LOW密码长度至少 8 位内网测试环境1 / MEDIUM长度 大写 小写 数字 特殊字符大多数业务环境2 / STRONG在 MEDIUM 基础上增加字典文件对比合规要求高的生产环境我的建议是有条件的生产环境至少选 MEDIUM安全要求高的选 STRONG。选择 STRONG 还需要在配置里指定字典文件路径例如[mysqld] validate_password.policySTRONG validate_password.dictionary_file/path/to/dictionary.txt需要特别说明的是安装这个组件只会对之后新设置或修改的密码进行校验不会强制要求已有的弱密码用户立刻改密。但如果你在跑脚本时选择了设置 root 新密码那么新密码必须满足刚才选定的策略否则直接报错。2.3 修改 root 密码的隐藏前提validate_password 组件的策略级别确定后脚本会提示Please set the password for root here. New password: Re-enter new password:这一步和前一步的“Enter current password for root”容易混淆注意区分前面是输入旧密码这里是输入新密码。如果当前 root 通过 auth_socket 认证Ubuntu/Debian 上常见直接输入旧密码回车反而会失败后面第三节会单独说这个问题。生产环境的 root 密码我建议用 16 位以上的随机字符串包含大小写字母、数字和特殊符号并且和业务账号密码完全分开。不要把 root 密码直接写在部署脚本里更不要为了省事用 123456 之类——在公网上被扫到只是时间问题。2.4 匿名用户最容易忽略的后门接下来脚本会问Remove anonymous users?匿名用户指的是用户名为空的账号例如localhost。它之所以危险是因为在默认授权规则下任何能从本机连接到 MySQL 的会话都不需要账号密码。虽然匿名用户通常只拥有 test 库的操作权限但它会干扰账号匹配逻辑。MySQL 在匹配账号时会按 user 和 host 的精确度排序匿名账号在某些场景下可能“抢”在正常业务账号前面被匹配到导致应用行为异常。我处理过一个线上问题应用连接池配置了账号app_user但实际连接时经常出现权限不足的报错。排查后发现就是mysql.user表里残留了匿名用户连接被匹配到了匿名账号上。删除匿名用户后问题消失。不同安装方式下匿名用户的存在情况不同。使用mysqld --initialize初始化的数据目录默认没有匿名用户所以有些版本在跑脚本时这一步不会出现或者提示“No anonymous users found”直接跳过或自动回答即可。这一项选择 y 时脚本会删除所有用户名为空的账号。建议在生产环境无条件执行。2.5 root 远程登录限制为什么脚本要删除非本机的 root 账号下一步提示Normally, root should only be allowed to connect from localhost. This ensures that someone cannot guess at the root password from the network. Disallow root login remotely?很多第一次跑的人会在这里犹豫我还需要用 Navicat / DataGrip 连数据库禁止 root 远程登录后是不是就没办法远程管理了这里要澄清一个核心概念MySQL 的账号是“用户名 主机”二元组rootlocalhost和root192.168.1.%是两个完全不同的账号。脚本选择 y 时实际执行的动作是删除Userroot且Host不在localhost、127.0.0.1、::1范围内的账号。也就是说它只删除 root 的远程登录渠道本机通过 socket 或 127.0.0.1 登录 root 仍然可用。远程管理数据库的正确做法不是开放 root 远程而是创建专用管理员账号比如CREATE USER ops% IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON *.* TO ops% WITH GRANT OPTION;如果之前已经手动创建了root%用于远程连接跑脚本时这一项选择 y远程连接会立刻失效这个后果要想清楚。2.6 test 数据库与通配授权记录脚本继续问Remove test database and access to it?默认安装下MySQL 会创建一个 test 数据库并在授权表里写入两条特殊记录Dbtest和Dbtest\\_%Host 为空字符串。这意味着任何用户都能访问 test 库包括前面的匿名用户。test 库虽然看起来只是拿来“测试”的但因为它无需授权即可读写攻击者在拿下一个低权限账号后可以把恶意数据或脚本文件放在里面作为横向移动的中转站。选择 y 后脚本会删除 test 库本身并删除所有test%通配授权记录。如果业务确实需要一个叫“test”的库后续可以手动创建并显式授权给指定账号而不是保持默认的“所有用户都能访问”状态。2.7 重新加载权限表让改动立即生效最后一步Reload privilege tables now?MySQL 在启动时会加载授权表到内存并对后续连接进行缓存匹配。直接修改mysql.user、mysql.db等表后如果不刷新已删除的账号可能仍在缓存中生效。脚本这一步实际上执行的就是FLUSH PRIVILEGES强制 MySQL 重新加载授权表让前面所有变更立即生效。到这里脚本执行完毕最后会看到All done!此时可以用新密码重新登录验证一遍并检查当前的账号和数据库状态。3. 实际部署中翻过车的地方排坑实录3.1 找不到临时密码或临时密码已过期很多从 RPM 或二进制包安装 MySQL 5.7 / 8.0 的同学安装完成后并不知道初始密码在哪。最常见的查看方式grep temporary password /var/log/mysqld.log如果日志里没找到可能是因为日志路径不同。可以检查这几个位置/var/log/mysql/error.log/var/lib/mysql/*.errjournalctl -u mysqld | grep password临时密码还有一个坑MySQL 5.7 之后默认配置了密码过期策略初始临时密码的有效期通常很短。如果你拿到临时密码后没有及时登录修改再登录时会直接报ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.这时候跑mysql_secure_installation也会卡在第一步因为当前密码虽然正确但会话处于“必须改密”状态。解决办法是先手动登录并修改一次密码mysql -uroot -p登录后执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;然后再跑mysql_secure_installation。3.2 Ubuntu 的 auth_socket 认证导致脚本卡在第一步在 Ubuntu / Debian 系统上通过 apt 安装的 MySQLroot 用户默认使用的是auth_socket认证插件而不是密码认证。表现是你在终端里直接执行sudo mysql就能进入数据库但问你 root 密码是多少时你根本不知道因为从来就没设置过。这种情况下跑mysql_secure_installation第一步“Enter current password for root (enter for none)”直接回车或者随便输入都会报Access denied。处理方式先用sudo mysql进入数据库把 root 认证方式切换为密码认证ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 新密码; FLUSH PRIVILEGES;MySQL 8.0 默认认证插件是caching_sha2_passwordMySQL 5.7 及以下版本使用的是mysql_native_password。确认自己版本对应的插件切换之后再跑安全脚本就不会卡了。3.3 已经用 root 远程连接时跑脚本的后果我见过不止一次这样的操作应用已经上线了root 账号开着远程登录某天 DBA 想起来“应该跑一下安全脚本”于是顺手跑完结果远程应用立刻报连接失败自己本地的 GUI 客户端也连不上了。这不是脚本运行出错而是脚本按你的确认把 root 远程登录账号删掉了。此时通过 SSH 登录服务器用 socket 方式仍然能进入 MySQLmysql -uroot -p登录后如果你确实还需要 root 远程权限——注意业务上的规范操作是不要恢复 root 远程而是创建一个专用管理员账号CREATE USER ops% IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON *.* TO ops% WITH GRANT OPTION; FLUSH PRIVILEGES;如果因为某些老旧系统依赖 root 远程连接短期内可以先创建 root 的专用白名单账号例如只允许特定内网网段连接CREATE USER root192.168.100.% IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON *.* TO root192.168.100.% WITH GRANT OPTION; FLUSH PRIVILEGES;注意 Host 要尽量精确到网段或具体 IP而不是直接用%。这种操作只能在应急场景下用长期方案一定是业务账号最小权限 专用运维账号。3.4 Docker 容器里执行前必须想清楚的一件事用官方 mysql 镜像部署容器时很多人会通过环境变量初始化 root 密码docker run -d \ --name mysql \ -e MYSQL_ROOT_PASSWORDroot密码 \ -e MYSQL_ROOT_HOST% \ -p 3306:3306 \ mysql:8.0设置MYSQL_ROOT_HOST%后容器初始脚本会创建root%账号供远程连接使用。如果之后进入容器执行mysql_secure_installation并在“Disallow root login remotely”一步选择了 y那root%会被删除所有依赖容器 root 远程连接的应用都会挂掉。在容器里执行前先检查当前账号状态SELECT user, host FROM mysql.user;如果业务依赖容器内的 root 远程账号这两步不要选择禁止远程登录或者干脆不跑脚本改为手工执行“只删匿名用户、只删 test 库、只改密码”的部分。容器的网络边界本身有限容器端口默认不对外暴露除非手动映射安全重心应该在宿主机的防火墙和容器端口映射上。3.5 非交互执行自动化部署时的正确姿势很多初始化系统要求在无人值守的情况下完成安全加固。mysql_secure_installation是交互式脚本直接在自动化脚本里用管道喂答案在不同版本上表现不稳定。有些版本从 tty 读取输入管道输进去没效果然后脚本挂在那里超时。几个相对可靠的方向MySQL 8.0.34 及以上版本脚本本身提供了--use-default参数可以直接使用推荐默认值执行所有加固步骤mysql_secure_installation --use-default这个参数会自动采用“移除匿名用户、禁止 root 远程登录、移除 test 库、刷新权限表”的默认动作但仍然需要当前 root 密码。如果你的自动化平台已经知道当前 root 密码可以直接配合--defaults-extra-file使用。更灵活的方式是用 expect 脚本模拟交互式输入。给出一个基础模板#!/usr/bin/expect -f set timeout 60 set current_pwd 旧的root密码 spawn mysql_secure_installation expect Enter current password for root send $current_pwd\r expect VALIDATE PASSWORD send n\r expect Change the password for root send y\r expect New password send 新的root密码\r expect Re-enter new password send 新的root密码\r expect Remove anonymous users send y\r expect Disallow root login remotely send y\r expect Remove test database send y\r expect Reload privilege tables send y\r expect All done注意不同版本脚本的提示文字略有差异直接用前先在本机跑一遍把 expect 中期望的提示文本调整准确。expect 脚本的密码会明文保存在文件里注意文件权限至少chmod 700。4. 脚本之外的加固思路把它当作安全基线的起点4.1 等价 SQL 手工加固清单在某些场合手动执行等价 SQL 比跑脚本更可控。比如你已经知道自己的环境里有哪些账号需要保留哪些是历史遗留的逐条 SQL 比交互式脚本灵活得多。下面是一套与mysql_secure_installation常见操作等价的 SQL 清单实际执行前先备份-- 1. 删除匿名用户 DELETE FROM mysql.user WHERE User; -- 2. 删除 test 数据库及通配授权 DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Db LIKE test%; -- 3. 删除 root 的远程登录账号保留 localhost / 127.0.0.1 / ::1 DELETE FROM mysql.user WHERE Userroot AND Host NOT IN (localhost, 127.0.0.1, ::1); -- 4. 修改 root 密码密码需满足密码策略 ALTER USER rootlocalhost IDENTIFIED BY 新的强密码; -- 5. 刷新权限表 FLUSH PRIVILEGES;需要注意MySQL 8.0 中不要直接去 UPDATEmysql.user表的authentication_string字段密码哈希格式和认证插件绑得很紧手工改错会导致无法登录。设置密码统一用ALTER USER。如果想要预估这条 SQL 是否会影响现有连接执行前可以先查一遍SELECT user, host FROM mysql.user WHERE userroot; SELECT user, host, db FROM mysql.db WHERE db LIKE test%; SELECT user, host FROM mysql.user WHERE user;4.2 与脚本互补的几个 MySQL 安全项mysql_secure_installation只覆盖了账号层面的基础问题。真正能提高数据库防线水平的是下面这些脚本不做但同样重要的事。限制监听地址在my.cnf中设置bind-address127.0.0.1可以让 MySQL 只监听本机接口。如果业务必须在局域网内被访问就绑定内网 IP不要让数据库端口暴露到公网网卡上。业务账号最小权限不要给应用账号ALL PRIVILEGES ON *.*。一个只操作orders库的账号权限应该是CREATE USER app% IDENTIFIED BY 强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON orders.* TO app%;关闭不必要的 LOCAL INFILE配置文件里加上[mysqld] local_infileOFF可以防止通过LOAD DATA LOCAL INFILE读取客户端本地文件。这个点在等保测评和渗透测试里经常被检查。二进制日志开启 binlog 不仅能做时间点恢复也是主从复制的基础。结合mysql_secure_installation加固过的实例建议同步检查SHOW VARIABLES LIKE log_bin; SHOW VARIABLES LIKE binlog_format;如果涉及主从复制强烈建议不要用 root 账号做复制用户只建一个最小权限的复制账号CREATE USER repl% IDENTIFIED BY 强密码; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl%;4.3 多实例和 MariaDB 环境下的一点经验如果你管理的服务器上跑着多个 MySQL 实例每个实例的 socket 路径和端口都不同。直接执行mysql_secure_installation时脚本默认连接某个固定路径的实例不一定是你想加固的那一个。为每个实例执行加固时最好显式指定 socketmysql_secure_installation --socket/data/mysql3307/mysql.sockMariaDB 也自带mysql_secure_installation交互流程和 MySQL 大体相似但细节上有差异比如 MariaDB 10.4 默认的 root 认证方式、unix_socket 插件的介入导致“输入当前 root 密码”这一步的处理方式不同。MariaDB 环境不要完全照搬 MySQL 的步骤先SELECT user, host, plugin FROM mysql.user;看一眼实际状态再操作。我的个人习惯是把这些加固步骤直接写进服务器初始化脚本里用 SQL 清单 系统防火墙规则组合完成而不是每次手动跑交互式脚本。这样既能保证所有新实例基线一致也避免哪次漏跑了一步。一个刚装的 MySQL 实例跑完mysql_secure_installation之后用SELECT user,host,plugin FROM mysql.user;看结果root 只留下本机登录、没有任何匿名用户、test 库不存在才算是真正到了可以创建业务账号、接入应用的阶段。
返回列表