ARTICLE DETAIL

资讯详情

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

mysql_secure_installation 详解:MySQL安全基线配置实践

mysql_secure_installation 详解:MySQL安全基线配置实践 在Linux服务器上装完MySQL接下来的第一件事几乎都绕不开mysql_secure_installation这个安全脚本。我见过不少新同学在教程里看到这行命令就直接跳过理由是我本机用怕什么。直到某次一台没跑安全脚本的测试机被扫描器直接拖走数据我才彻底意识到这一步挡掉的麻烦远比想象中多。这篇文章就从这个脚本本身讲起把每个交互问题背后的原理、执行顺序、以及我实际部署中踩过的坑完整记录下来照着操作基本不会出岔子。我需要先说明一点这篇文章以MySQL 8.0系列为准5.7版本虽然交互文案略有差异但核心逻辑完全一致照读也能对上号。1. 为什么安装完MySQL第一件事就是跑安全脚本1.1 默认安装下的MySQL到底有多不安全很多发行版比如CentOS、Ubuntu通过包管理器安装MySQL后是不会主动给你设置一个强密码的。在MySQL 5.7之前root密码默认就是空的也就是说任何人只要在本机执行mysql -uroot就能直接进数据库。5.7之后虽然改成了安装时自动生成临时密码但依然存在几个历史遗留问题。最典型的就是匿名用户。MySQL在初始化数据目录的时候会创建一批默认账号其中包括User字段为空的匿名账号配合一个默认的test数据库任何用户、任何主机都能不登录就访问这台服务器的数据库还能读写test库。这在开发环境下可能感觉不出来但一旦服务器暴露在公网扫描器只要识别到3306端口开放就能用匿名账号尝试登录成功率极高因为根本不需要密码。另外早期MySQL的root账号虽然默认只允许localhost登录但部分发行版打包时会在mysql.user表里额外插入一条允许远程登录的root记录或者允许从服务器主机名访问。这意味着一旦root密码被暴力破解攻击者可以在任意一台机器上直接连接并拿到整个数据库的控制权。这三点凑在一起就是为什么MySQL官方会在安装后的最后一步强烈建议你执行安全脚本。1.2 这个脚本核心解决的五个问题mysql_secure_installation本质上是一个基于Perl或Shell的交互式脚本安装MySQL的时候会被放置在bin目录下。它做的事情可以概括为五件事检查并设置root密码强度如果root密码为空或过弱会引导你重新设置删除匿名用户账号禁止root账号从非本机地址远程登录删除默认的test测试数据库及相关权限记录重新加载权限表使上述修改立即生效这五件事单独拎出来看都很简单但组合在一起就是一套最小化安全基线。执行完后MySQL的权限模型会收敛成只有明确的用户、从明确的主机、用明确的密码才能访问把所有历史默认痕迹全部清掉。我实际测试过在一台默认安装的MySQL 8.0服务器上执行脚本前mysql.user表里除了root还有一两条匿名记录执行完后干净得像刚初始化过一样攻击面会明显缩小。2. 执行前的准备工作和执行入口2.1 什么时候执行这个脚本最合适最理想的时机是在MySQL刚刚初始化完成、还没有任何业务接入的时候。这时候权限表相对干净root密码要么没设置过、要么是临时密码脚本可以直接从初始状态接管并设置基线。如果你接手了一台已经跑了很久的MySQL只要你是root用户或者有sudo权限随时可以执行这个脚本。它不会动你已有的业务账号和数据只会处理匿名用户、test库和root账号的登录策略对线上运行基本没有破坏性。不过有两个注意点第一如果你依赖匿名用户做某些应用的连接执行脚本后这些应用会直接报Access denied第二如果root密码不是你设置的你需要先拿到当前root密码否则脚本第一步就会卡住。另外我建议不要在业务高峰期执行因为脚本最后会刷新权限表虽然FLUSH PRIVILEGES本身开销很小但如果正好赶上大量新建连接还是会有微小的锁竞争。图省事没问题但做任何操作都有最佳窗口期。2.2 执行需要什么权限和连接条件执行这个脚本需要使用能访问MySQL服务器的系统用户通常是root或者mysql用户配合sudo来执行。脚本会通过Unix套接字连接到本地的MySQL服务所以必须确保MySQL服务已经启动并且socket文件在默认位置。如果你用的是源码编译安装socket文件路径可能不在默认位置脚本有可能连不上。这时候可以看脚本支持的参数或者先手动设置好socket路径。不同发行版打包的脚本参数不完全一样最通用的做法是在命令行指定配置文件mysql_secure_installation --defaults-file/etc/my.cnf这里要注意--defaults-file指向的是MySQL客户端的配置文件脚本会从中读取socket连接信息。如果还是提示失败直接看my.cnf里[client]段的socket路径是否和[mysqld]段的一致这是我踩过的第一个坑源码编译安装默认socket在/tmp/mysql.sock而yum安装默认在/var/lib/mysql/mysql.sock两者对不上就会报错。3. 脚本交互过程的完整拆解3.1 从输入root密码开始脚本执行后会先提示输入当前root密码。如果你是从未设置过密码的状态跑这个脚本直接回车就好。如果之前设置过临时密码则需要输入那个临时密码。这一步的目的不是验证你的身份验证身份是通过socket完成的而是为了让脚本有权限去修改root账号的策略。你输入的密码会用来建立数据库连接如果密码不正确脚本会直接终止并提示Access denied for user rootlocalhost。我刚提到5.7之后安装时会生成临时密码位置一般在/var/log/mysqld.log或者通过grep temporary password /var/log/mysqld.log找到。很多人第一次执行脚本就卡在这一步因为根本不知道临时密码在哪里。这里给出一个快速查看的方法grep -i temporary password /var/log/mysqld.log如果MySQL是docker容器部署的临时密码通常直接显示在容器启动日志里用docker logs 容器名就能看到。3.2 是否安装密码校验组件在输入正确密码之后MySQL 8.0的脚本会询问是否安装VALIDATE PASSWORD COMPONENT。这其实是一个可选的密码强度校验插件安装后可以在MySQL层面对新设置的密码做复杂度检查而不是单纯依赖应用层自觉。从安全角度看我每次都选择安装因为它是分文不花就能提升基线水平的手段。特别是当你管理多台服务器时手工保证每个库的密码都符合复杂策略几乎不可能交给插件来做最省心。脚本接下来会要求选择密码校验强度通常有三个等级等级名称校验规则0LOW密码长度不少于8位1MEDIUM长度不少于8位须包含数字、大小写字母2STRONG长度不少于8位须包含数字、大小写字母、特殊字符这里我明确推荐选择2STRONG尤其是面向生产环境的服务器。很多人嫌特殊字符记不住于是选LOW但LOW等级下其实随便一个8位小写字母组合就能通过和没装插件也差不太多。我个人习惯的生产密码规格是16位以上包含大小写、数字和特殊符号这样即使数据库泄露暴力破解成本也高得多。如果你选MEDIUM或STRONG后续设置root新密码时就必须满足对应条件否则会报错让你重试。这里有个小技巧如果你不想用交互式选择可以在执行脚本前先加载插件并设置策略等级脚本检测到已配置好的密码策略后询问逻辑会跳过一部分。3.3 设置root新密码接下来脚本会提示你输入新的root密码并确认一次。这里有几个实际经验值得说道。第一MySQL 8.0默认的密码认证插件是caching_sha2_password密码的校验规则比5.7严格。如果你是从旧版本升级上来的设置新密码的时候偶尔会提示不符合历史策略这时需要先检查全局的validate_password参数。第二新密码会直接通过ALTER USER rootlocalhost IDENTIFIED BY ...生效。正常情况下这一步会很快但如果你的MySQL配置了复杂的密码校验或者使用了PAM认证可能需要多等待一小会儿。第三千万记住这里设置的是root的登录密码和你执行脚本用的系统shell账号密码不是同一个概念不要图省事设置成一样的。我在实际部署中遇到过一种情况设置新密码时明明通过h了校验但脚本退出后mysql -uroot -p却死活连接不上。排查半天发现是脚本又走了一次密码策略协商实际上新密码已经设置成功只是客户端那边连接参数不对比如指定了错误的socket文件。连接不上不要急着怀疑密码先确认连接方式再说。3.4 移除匿名用户脚本会问Remove anonymous users?默认是Y。这一步背后做的事情是删除mysql.user表里所有User字段为空的记录。为什么匿名用户必须删原因很简单MySQL在没有明确匹配到用户名时会回退匹配匿名账号。你辛辛苦苦给某个应用创建了带密码的账号但如果服务器上存在匿名账号且该账号的主机匹配范围更宽客户端就可能以匿名身份登录整个权限体系就被绕了过去。这在多租户场景下尤其危险因为不同应用共用一台MySQL时匿名账号的存在会让访问控制形同虚设。实际操作上删除匿名用户后任何没有密码的连接都会直接报错这对业务是透明的不会影响正常账号。但要注意如果你的应用正好是使用了无密码连接的老旧系统删除匿名用户会导致应用直接不可用。所以执行前最好先看一下mysql.user表里有哪些账号确认哪些是业务在用的。查询方法很简单SELECT User, Host FROM mysql.user;见到User为空的记录毫不犹豫删掉就对了。3.5 禁止root远程登录接下来脚本会问Disallow root login remotely?同样是默认Y。这个选项的意思是修改root账号在mysql.user表中的Host字段只保留localhost或本机主机名的登录权限禁止从192.168.x.x、10.x.x.x等远程IP发出root连接。为什么要这么限制root是MySQL的超级用户一旦允许远程登录攻击者只需要破解一个root密码就能从地球另一端直接接管整个数据库。而限制成本本地登录后攻击者必须先拿到服务器的shell权限才能进一步操作数据库攻击链明显拉长。这里我强调一句禁止root远程登录不代表管理员不能远程管理数据库。正确的做法是在服务器上通过root登录MySQL创建一个专用的管理员账号比如admin授予必要的权限然后通过这个账号远程连接。这样既保留了远程管理能力又把批量爆破root密码的风险降到最低。实际操作中很多人执行完脚本后发现Navicat等工具连接不上就是因为选了禁止root远程。解决办法不是重新开启root远程而是建一个专用账号这是合规且安全的标准操作。3.6 删除test数据库和权限脚本接下来问Remove test database and access to it?默认Y。这一步删除的是test库以及与之相关的权限记录。test库是MySQL初始化时自动创建的一个虚拟数据库本意是用来给新手练习的。问题在于它没有任何访问限制所有用户即使没有显式授权都能在这个库里创建、删除、修改表。一台服务器上有多个业务账号时某个低权限账号完全可以利用test库做数据中转、写病毒脚本或者消耗磁盘空间。删除test库是安全的操作因为正常业务不会使用test库做存储。就算你有个测试环境叫test_db那也不是test库本身脚本只删名字为test的默认库以及test_%开头的通配授权记录不会误伤你自己创建的库。从我经历过的案例来看很多被入侵的MySQL服务器上攻击者留下的持久化脚本都藏在test库里因为操作无痕且有写权限。所以在生产环境test库几乎是必须清理的对象。3.7 重载权限表最后一个问题是Reload privilege tables now?默认Y。这里执行的本质就是FLUSH PRIVILEGES。MySQL的权限是级联缓存在内存里的直接修改mysql.user、mysql.db等表后如果不同时刷新权限缓存已经连接的会话可能还用着旧的权限。FLUSH PRIVILEGES会强制服务器重新读取grant表让之前删除匿名用户、修改root Host、移除test权限这些操作立即生效。这一步基本没有副作用唯一的影响是极短时间内权限表的读操作可能被阻塞。要知道MySQL的权限检查非常频繁每个连接建立时都会查权限表刷新时如果有大量并发连接理论上会有极小的延迟。所以我不建议在每秒数百新建连接的高峰期执行。做到这里安全脚本的交互过程基本结束执行完会看到一行All done!说明最小安全基线已经打好了。4. 非交互式执行与自动化部署4.1 用管道方式一键执行如果你管理的服务器很多一台台跑交互脚本能累到怀疑人生。mysql_secure_installation支持通过标准输入重定向来模拟交互回答常见做法是用printf构造一组答案管道传给脚本。比如在MySQL 8.0上如果当前root密码为空且你希望选择安装密码校验组件、密码强度选STRONG、删除匿名用户、禁止root远程、删除test库、重载权限表可以用下面的命令printf n\nY\n2\nNewPassw0rd!\nNewPassw0rd!\nY\nY\nY\nY\n | mysql_secure_installation但这条命令有一个坑脚本版本不同交互顺序可能不同多传或少传一个输入都会导致流程错乱。8.0.36在我的测试机上这个顺序是成立的但5.7版本因为不询问密码校验组件答案个数就不一样必须逐版适配。如果脚本支持--use-default参数事情就简单得多。MySQL 8.0.16之后的部分发行版打包版本提供这个参数它直接按所有选项的默认值即全部选Y执行。命令是mysql_secure_installation --use-default但这个参数的前提是你已经设置好root密码否则会在第一步卡住。4.2 通过配置文件预置答案在自动化场景里我更推荐的做法是直接把安全基线写进配置或脚本里而不是依赖交互模拟。比如用expect脚本或者直接在部署流程里手工拼SQL因为脚本最后做的那些事情本质就是几条SQL语句完全可以自己复现。手动复现的SQL等价于ALTER USER rootlocalhost IDENTIFIED BY 新密码; DELETE FROM mysql.user WHERE User; ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 新密码; DROP DATABASE IF EXISTS test; DELETE FROM mysql.db WHERE Db LIKE test%; FLUSH PRIVILEGES;这样做的好处是精准可控不用适配不同版本脚本的交互差异也不依赖MySQL打包方的脚本实现。很多运维平台的MySQL初始化模块就是这么做的先初始化实例再用SQL语句去收敛基线比依赖一个交互式脚本更工程化。如果你的公司已经有配置管理工具比如Ansible、SaltStack把这几条SQL放到init脚本里服务器交付后自动执行比手动跑一万次安全脚本高效得多。4.3 Debian/Ubuntu下的参数化预配置在Debian系下mysql_secure_installation往往由debconf驱动可以通过预置debconf配置来免交互。比如可以用debconf-set-selections提前把密码和策略设置好。但说实话这种方式在不同版本间兼容性一般我不建议过度依赖倒是可以看看流程中能否把safeinstall步骤完全交给配置管理工具。从工程效率角度讲真正可靠的自动化安全加固方案是初始化MySQL后统一执行手工SQL基线然后定期用扫描脚本检查匿名用户、空密码、test库是否存在。这比任何交互式工具的自动化都要稳。5. 执行过程中的常见问题与排查记录5.1 不知道root当前密码导致第一步卡死这是遇到频率最高的问题尤其是在MySQL 5.7之后。解决思路有两个如果MySQL是通过yum/apt安装的二进制包临时密码一定写在日志里如果日志里找不到或者你接手的是别人部署的实例可以绕过密码认证直接进入MySQL重设密码。具体做法是临时停掉MySQL服务在配置文件的[mysqld]段加入skip-grant-tables启动这样就可以免密登录然后执行ALTER USER rootlocalhost IDENTIFIED BY 新密码重设密码。需要注意操作完成后必须立刻移除skip-grant-tables配置并重启否则服务器一直处于不设防状态比不设密码还危险。这个方案有一个变体在MySQL 8.0里如果你在Ubuntu上以root身份执行sudo mysql可以直接进原因是默认认证方式用了auth_socket插件系统用户是root就自动识别。这时你可以直接重设密码而不必重启服务。不同发行版行为有差异但思路是一致的。5.2 交互中提示密码强度不足选择STRONG强度后设置新密码会执行validate_password检查如果不符合规范就直接拒绝。我遇到过密码里包含了服务器名等字典词汇而被拒绝的情况因为STRONG级别会做字典匹配不只是简单的大小写和位数检查。遇到这种问题最快的解决方式是换一个更随机、更长的密码或者临时降低策略等级SET GLOBAL validate_password.policy LOW;改完后再执行脚本设置完密码后记得改回来。注意MySQL 8.0里组件的变量名带了前缀validate_password.5.7则直接叫validate_password_policy不要搞混。5.3 执行完远程连接还是失败很多人以为执行安全脚本后再用root从远程连一次就能成功结果还是失败。这其实是正常的因为脚本默认把root的Host限制在本地了你远程连接根本匹配不到账号。正确的远程管理方式是创建一个专门的管理用户授予对应权限。比如要创建一个可以从公司内网IP段访问的管理员CREATE USER dba192.168.10.% IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON *.* TO dba192.168.10.% WITH GRANT OPTION; FLUSH PRIVILEGES;这里我再提醒一句创建完专用账号后用这个账号测试一下能正常登录再删掉root的远程权限避免把自己锁在外面。5.4 脚本报错“Cant connect to local MySQL server”这个报错通常不是脚本本身的问题而是MySQL没有启动或者socket路径不一致。先用systemctl status mysqld确认服务状态再用mysqladmin ping测试连接。如果服务运行正常但脚本还是连不上查看my.cnf里socket路径或者直接用--socket/具体路径参数指定。我曾经在一台源码编译安装的实例上遇到过脚本用的是/var/lib/mysql/mysql.sock但实际socket在/tmp/mysql.sock导致脚本执行前就失败。复制一份socket文件到默认位置不是长久之计正确做法是软链接或直接用参数指定定义好标准路径后无论脚本还是客户端访问都用一处避免以后踩同样的坑。6. 安全脚本之外还需要做的几件事6.1 网络层面的访问控制安全脚本只处理MySQL内部的权限不会帮你做网络隔离。生产环境上我始终强烈建议把bind_address绑定到内网IP而不是0.0.0.0然后在操作系统防火墙层面只放行需要的IP段访问3306端口。这两层网络收敛以后等于在数据库最外层加了双重滤网即使MySQL内部账号泄露攻击者也未必能触达服务端口。原理上很简单MySQL走TCP监听bind_address决定监听的网卡IP。如果一台服务器有多个IP只绑定内网IP就能避免公网网卡上的连接请求。做完之后再配合防火墙策略把数据库对公网的暴露降到接近零。6.2 账号最小权限与独立端口不要因为省事就每个应用都用root账号连接。正确做法是一个应用一个账号账号只授权它需要的库只授权它需要的权限。比如订单服务只给订单库的SELECT/INSERT/UPDATE/DELETE报表服务只给某几个库的SELECT。这样即使某个应用被攻破数据库内部的横向移动也会被权限挡住。关于端口这里多说一句把MySQL默认的3306改成其他端口是种常见的通过隐藏实现安全的做法效果有限但能显著减少扫描器的噪音扫描。我习惯改到高端口再配合防火墙能挡住大部分无差别扫描。6.3 日志与审计的打开安全脚本不会帮你开日志但日志往往是发现问题的第一道防线。至少开启通用查询日志或者审计日志如MySQL Enterprise Audit配合慢查询日志一起基本覆盖了事后溯源的需求。日志文件要定期轮转不然磁盘被撑爆数据库也会崩溃。另外别忘了关注MySQL连接日志里的异常失败记录。我见过一次暴力破解的尝试日志里同一个IP在几小时内连续尝试了几千次登录如果没有日志被攻破都不知道是哪一秒的事。整体看下来mysql_secure_installation是一个很小但极关键的步骤它把MySQL的默认状态收敛到了一个相对安全的最小基线。但从我这些年运维的经验来看真正的数据库安全从来不是一条命令就能搞定的脚本只是起点后续的账号体系设计、网络隔离、日志审计才是日复一日要做的事。建议在每次初始化新实例时把安全脚本的执行结果、手动加固SQL和检查记录都写进标准操作流程里形成固定动作这样才不容易在凌晨三点部署新库的时候漏掉任何一个环节。
返回列表