
去年做安全审计时接手了一台被入侵的 MySQL 服务器。入侵者其实只拿到了一个低权限 Web 应用数据库账号却差点把服务器上的敏感文件读走。追查之后发现数据目录里多了一条指向 /etc/passwd 的符号链接而实例的 symbolic_links 变量还保持着默认的 ON。那一刻起我就意识到这条经常被人忽略的配置其实是一扇很容易被撬开的侧门。这篇文章就从那次排查出发把 symbolic-links 是什么、为什么默认开启不安全、在 Linux 和 Windows 下分别怎么禁用、禁用之后会出现哪些问题完整地梳理一遍。适合正在做 MySQL 安全基线、等保整改或者只是想把数据库默认配置收敛一遍的运维、DBA 和开发同学参考。1. 符号链接从磁盘优化工具到攻击跳板的一步之遥1.1 MySQL 里的符号链接到底在干嘛先给不熟悉的朋友补个基础。符号链接symbolic link可以理解成文件系统层面的快捷方式一个文件本身不存数据只是指向另一个真实路径。Linux 里的 ln -sWindows 里的 mklink干的都是这件事。数据库里引入符号链接本意是解决磁盘规划和热数据迁移的问题。在 MySQL 5.7 及更早版本里symbolic-links 参数控制的是 MyISAM 引擎表文件的链接能力。比如一张大表在 /var/lib/mysql 所在磁盘放不下了DBA 可以把它的 .MYD、.MYI 文件挪到另一块盘上再在原位置放一根符号链接指过去MySQL 查询时照常工作看起来表还住在原来的数据库目录里。binlog、错误日志、慢查询日志这些文件也可以用类似思路放到独立磁盘避免和数据目录抢空间。对当时的运维来说这确实是低成本解决磁盘压力的一种手段。这里有个容易混淆的点要提前说清楚MySQL 内部的 symbolic-links 机制和操作系统层面直接把整个 /var/lib/mysql 软链到其他分区是两回事。前者由 MySQL 配置项控制后者是系统管理员用 ln -s 做的目录级软链。禁用 symbolic-links 后前者失效后者依然成立。很多人一听到禁用符号链接顺手把数据目录的软链也拆了其实没必要。另一个重要限制是InnoDB 的表空间文件.ibd从设计上就不支持这种表文件级符号链接symbolic-links 主要影响的是 MyISAM 表以及 5.7 里用 DATA DIRECTORY 指定的 InnoDB 外部表空间文件。所以如果你库表基本都是 InnoDB平时也没人会手贱去 ln -s 表文件那禁用这个参数通常不会带来任何业务影响。1.2 一个默认开启的低调参数现在说说这个参数为什么低调。很多生产环境的配置是从网上各种一键安装脚本或者最初部署时复制下来的my.cnf 里干干净净连 symbolic-links 这几个字都搜不到。但搜不到不代表没生效——配置文件里没写就使用编译或 RPM 包自带的默认值。在不少 5.7 发行版里这个默认值恰恰是开启的mysql SHOW VARIABLES LIKE symbolic_links; ----------------------- | Variable_name | Value | ----------------------- | symbolic_links | ON | -----------------------看到 ON 的瞬间很多 DBA 第一反应是我从来没开过啊。对这就是问题所在你以为是默认安全实际上默认配置只是能用离安全差得很远。一些官方 RPM 的 /etc/my.cnf 模板里其实已经写好了 symbolic-links0 并附带注释建议禁用以避免安全风险但如果你用的是源码编译、自定义安装或者网上某些精简过的自动部署脚本这行配置就很容易消失。无论是 CIS 的 MySQL 安全基线还是国内常见的等保整改要求禁用符号链接都被明确列为检查项。我见过更夸张的情况安全扫描报告明明已经标红说是 MySQL 符号链接高危结果排查后发现配置文件里连这个参数都没有全局变量却长期跑在 ON。值班同学对着报告查了半天最后才发现是默认值惹的祸。这也是为什么我一再强调做安全基线不能只看配置文件里写了什么一定要在实例上查实际运行的变量值。1.3 攻击路径拆解攻击者能看到你不想让它看到的文件符号链接的危险在于它把 MySQL 进程的文件读写能力和攻击者的控制力桥接了起来。MySQL 服务在 Linux 上通常以 mysql 用户运行这个用户权限不高但能读写的文件范围并不小Web 站点配置、应用日志、数据库备份文件、其他库的数据文件只要属主或权限允许mysqld 都能碰。攻击路径一读敏感文件。假设攻击者通过 SQL 注入或弱口令拿到了某个数据库账号这个账号恰好有 FILE 权限很多老业务为了避免权限问题直接给了同时数据目录可写。攻击者就能创建一张 MyISAM 表把表文件指到 /etc/passwd、/var/www/html/config.php 这类路径上再通过 LOAD DATA INFILE 或 LOAD_FILE() 把内容读出来。如果管理员在数据目录里存放了密钥、备份压缩包那就更致命了。攻击路径二写文件拿权限。更隐蔽、危害更大的做法是把符号链接指向攻击者想写入的位置。例如指向某个 Web 目录或者临时目录诱导 mysqld 通过 SELECT INTO OUTFILE 写入内容实现 WebShell 或后门。这里有个前提是 mysqld 进程要能写目标目录而 mysql 用户对很多系统目录并没有写权限所以攻击者会优先挑 mysql 用户可写的目录比如数据目录本身、临时目录、或者配置不当的 Web 上传目录。不要小看这条路径配合权限配置混乱的环境它就是提权到系统层面的跳板。我的结论很直接在攻击者站到数据库账号层面之后symbolic-links1 相当于把一扇本来关着的门给踹开了。禁用它不是解决所有问题但能干净利落地堵住这一类读、写任意文件的把戏。2. 动手前先摸底别在没搞清现状时盲目重启2.1 三分钟确认当前状态改配置之前第一件事不是改而是查。查实例当前变量查文件系统里到底有没有已经存在的符号链接两头都要看。SQL 层面连上 MySQL 执行SHOW VARIABLES LIKE symbolic_links; SHOW VARIABLES LIKE have_symlink;第一个是运行时配置禁用后应为 OFF第二个表示当前二进制是否编译支持符号链接这个一般是只读的看到 YES 不代表危险真正的风险点在于运行时参数开着且实际存在链接。有些老版本的变量名可能有细微差异不过 5.x 上这两个名字基本稳定。文件系统层面重点看数据目录ls -la /var/lib/mysql/ | grep ^l find /var/lib/mysql/ -type l -ls 2/dev/null第一类输出是数据目录下的直接软链比如某个表文件或子目录是链接第二类会递归找出更深层目录中的链接。如果两类输出都是空的说明当前虽有开启能力但还没被实际利用这时直接加配置禁用风险最小。如果输出里确实躺着几条链接就要先搞清楚它们指向哪里、是谁创建的、业务是否依赖再谈禁用。2.2 禁用到底会影响哪些正常功能禁用 symbolic-links 后受影响的场景要分清主次。最直接的是 MyISAM 表文件链接如果某张 MyISAM 表的 .MYD/.MYI 是通过软链放到其他目录的禁用后 MySQL 可能启动时报错找不到表或者查询时直接给你一个ERROR 1017 (HY000): Cant find file。其次是 5.7 里 InnoDB 表通过CREATE TABLE ... DATA DIRECTORY外置 .ibd 文件的场景这类表在数据目录里会生成指向外部文件的符号链接禁用后同样可能访问失败。不受影响的反而是要明确说出来的错误日志、慢查询日志、binlog 这些文件只要你是通过 log_error、slow_query_log_file、log_bin 等参数直接指定了绝对路径它们放哪个盘都行和符号链接机制无关。整个 datadir 是系统目录级软链的场景我在前文也提过那属于操作系统层面的软链MySQL 的 symbolic-links 参数管不到。所以摸底时真正要做的是回答一个问题当前实例有没有任何表文件依赖符号链接有的话逐条列清单然后按第 5 章的处理流程把这些表迁移成真实文件。没有的话直接禁用运行五分钟后再看监控即可。2.3 5.7 与 8.0 的差异为什么 8.0 里找不到这个配置数据库版本不同处理方式完全不一样。我用一张表把关键差异列出来项目MySQL 5.7 及更早MySQL 8.0symbolic-links 参数存在可配置已移除不再识别默认情况部分发行版编译和打包默认为开启不支持符号链接表文件级软链MyISAM 可用InnoDB 有限支持外部表空间不再支持相关语法和机制也一并调整在 my.cnf 中写入可写 symbolic-links0写入会导致启动报 unknown variable推荐动作显式禁用并清理已有链接从旧配置中删掉该行说白了MySQL 官方也意识到符号链接在安全上的风险高于带来的便利干脆在 8.0 里把这条路给封死了。如果你的业务跑在 8.0 上其实不用做禁用这个动作但要记得从旧版迁移配置时把 symbolic-links 相关行删掉否则启动直接失败。如果还停留在 5.7那就老老实实通过配置项关闭同时把底层的链接文件清理干净。3. Linux 下禁用 symbolic-links配置、覆盖与验证3.1 配置文件位置与优先级Linux 上 MySQL 读取配置的路径和顺序并不唯一常见的有 /etc/my.cnf、/etc/mysql/my.cnf、/usr/etc/my.cnf 以及用户家目录下的 .my.cnf。具体到你的机器可以用下面的命令确认实际读取顺序mysql --help | grep -A 2 Default options mysqld --verbose --help 2/dev/null | grep -A 1 ^my.cnf输出里列出的路径按顺序读取后面的配置会覆盖前面的同名参数。实际操作中我踩过一个坑明明在 /etc/my.cnf 里写了 symbolic-links0重启后查变量还是 ON。折腾半天才发现 /etc/my.cnf 末尾有一行!includedir /etc/my.cnf.d/而这个目录下的某个 .cnf 文件里又写了 symbolic-links1后读的配置把前面的覆盖了。所以改配置时建议先确认两个事一是实例到底用了哪些配置文件二是所有相关配置片段里有没有重复定义。稳妥起见把 symbolic-links0 写到最后被读取的那个文件里或者确保所有文件中的定义方向一致。systemd 环境下还可以用systemctl cat mysqld看一眼 ExecStart 有没有带参数命令行参数优先级最高如果服务脚本里直接传了 --symbolic-links1那改配置文件是无效的需要同步修正。3.2 参数到底该写 symbolic-links0 还是 skip_symbolic_links在 [mysqld] 段下两种写法我见过symbolic-links0 和 skip_symbolic_links。它们的含义基本等价从 5.7 文档来看都属于布尔型配置ON/OFF、1/0、TRUE/FALSE 都可以。我个人偏好写 symbolic-links0理由很简单审计时一眼就能看懂是想把开关置为关闭而 skip_ 开头的写法容易让人误以为是从未支持反而引发困惑。配置示例[mysqld] symbolic-links0如果是模板脚本批量下发也可以顺带把 have_symlink 测一下。不过要注意 have_symlink 是编译期能力不是运行时开关脚本里如果拿它当判断条件会踩坑。正确判断标准只有一个变量 symbolic_links 是否等于 OFF。另外这个变量不是动态变量改完配置必须重启实例才会生效不要指望像 wait_timeout 那样 SET GLOBAL 一把就完事。3.3 重启、验证与回滚改完配置不是完事重启和验证才算闭环。先备份配置文件再做干净利落的重启cp /etc/my.cnf /etc/my.cnf.bak.$(date %F) systemctl restart mysqld重启后第一时间确认服务状态和数据库可用性systemctl status mysqld --no-pager mysql -uroot -p -e SHOW VARIABLES LIKE symbolic_links; mysql -uroot -p -e SELECT COUNT(*) FROM mysql.user;期望结果服务 activerunningsymbolic_links 显示 OFF基础查询正常。如果服务起不来先看错误日志常见的是数据目录里残留表文件符号链接导致 MyISAM 或 InnoDB 打不开相关表这时按第 5 章流程处理即可。回滚也简单把备份的 my.cnf 恢复回去再重启一次变量就会回到 ON。但我要多说一句如果已经发现表文件依赖符号链接回滚只是临时止血正确收尾还是得把链接换成正身文件否则后面升级、迁移、加从库都会继续埋雷。4. Windows 环境下的符号链接隐患与处理4.1 my.ini 修改与服务重启Windows 下的处理思路类似但细节差异不少。MySQL 在 Windows 上的配置文件叫 my.ini常见位置是安装目录比如 C:\Program Files\MySQL\MySQL Server 5.7\my.ini或 C:\ProgramData\MySQL\MySQL Server 5.7\my.ini。注意 ProgramData 通常是隐藏目录资源管理器里看不到直接输路径最快。用管理员身份打开文件在 [mysqld] 段下加一行symbolic-links0保存后重启服务。命令行方式net stop MySQL57 net start MySQL57服务名不一定是 MySQL57用管理员 PowerShell 执行Get-Service -Name *mysql*看实际名字。重启完同样用 SHOW VARIABLES LIKE symbolic_links; 验证期望 OFF。4.2 Windows 符号链接的权限模型别以为默认就没风险Windows 创建符号链接默认需要 SeCreateSymbolicLinkPrivilege 权限普通用户默认没有所以很多人会觉得Windows 上哪来符号链接攻击。这话对一半错一半。一方面Windows 上有另一种机制叫目录联接junction创建它并不校验 SeCreateSymbolicLinkPrivilege历史上有过不少工具利用这个缺口。另一方面Windows 10 之后的开发者模式允许普通用户执行 mklink 创建符号链接MySQL 服务如果以 LocalSystem 这类高权限账户运行攻击者一旦能控制 MySQL 数据目录仍然有机会构造链接指向系统关键位置。所以 Windows 上的结论是风险形式不同但别高估默认权限模型带来的安全感。禁用 symbolic-links 依旧是把可控性握在自己手里不依赖攻击者碰巧没有权限的运气。4.3 Windows 上容易忽略的附加问题Windows 上禁用符号链接后我见过两个额外困扰。第一个是杀毒软件和安全中心扫描数据目录时的误报问题某些防病毒软件对符号链接文件特别敏感经常误报或反复标记禁用后能少一类告警。当然这属于附带收益不能当主要动机。第二个是路径大小写和盘符混乱Windows 文件系统对路径处理比 Linux 宽容但也更容易出现链接文件指向移动盘、网络映射盘的情况禁用前用dir /AL或工具搜索一遍数据目录下的符号链接免得后面迁移时找不到文件。dir /AL C:\ProgramData\MySQL\MySQL Server 5.7\Data\ 2nul如果发现有链接文件需要在重启禁用配置之前处理做法和 Linux 一样先把真实数据复制到位删掉链接再用 CHECK TABLE 验证表完整性。5. 禁用后最常见的三个后遗症与排查记录5.1 启动后报错 1017遗留符号链接表文件先说一个我实际处理过的案例。某台 5.7 实例把一张历史归档的 MyISAM 表单独软链到了大容量磁盘禁用 symbolic-links 后重启服务起来了业务却报警错误日志里看到类似ERROR 1017 (HY000): Cant find file: ./history/archive_data.MYI (errno: 2)。排查过程其实不复杂。先停服务再在数据目录下用 find 把链接文件全部找出来确认每一条指向哪里。然后按下面的顺序处理停业务或只读开关保证没有写操作用 cp -a 把链接指向的真实文件完整复制回数据目录的对应位置删除原来的符号链接确认文件属主为 mysql:mysql权限 640 或参考原文件的权限启动 MySQL执行 CHECK TABLE history.archive_data; 验证这套流程的要点是复制而不是原地移动。先让正身文件落地确认新位置没问题再删链接风险最小。如果表是千万级的大表复制时间会比较长尽量安排在低峰期并提前评估磁盘空间。教训是禁用 symbolic-links 之前务必先做过一轮链接文件清理否则重启之后才面对一堆 1017很容易被业务催得手忙脚乱。5.2 MySQL 8.0 启动失败unknown variable symbolic-links0另一种典型情况是在升级到 8.0 时踩的。之前 5.7 的 my.cnf 里写了 symbolic-links0拿着这套老配置直接启动 8.0mysqld 直接拒绝启动[ERROR] [MY-000067] [Server] unknown variable symbolic-links0第一眼很懵这不是个安全开关吗怎么还会启动失败原因前面说过8.0 移除了符号链接支持参数不存在了MySQL 对未知参数默认按错误处理。这种情况不是要重新打开什么而是要把这一行配置删掉。处理完后用mysqld --validate-config或直接试启动验证一下配置合法性再启服务。建议所有准备做 5.7 到 8.0 升级的团队把老配置里已经废弃的参数统一清理一遍。除了 symbolic-links还有 query_cache_size 这类 8.0 不再支持的项也会在升级时冒出来。升级前 diff 一下 5.7 和当前 8.0 的默认模板比启动失败后一条一条试高效得多。5.3 批量清理数据目录中的符号链接如果数据目录里链接较多手动一条条处理太累可以写个命令先扫描、再人工确认。我常用的扫描命令find /var/lib/mysql -type l -ls 2/dev/null | tee /tmp/mysql_symlink_$(date %F).txt这条命令只扫描不处理重点是把结果输出到文件里人工核对。在确认没风险后可以用循环把指向数据文件.MYD、.MYI、.ibd的链接找出来逐个替换成真实文件。但是要注意不要盲目对每条链接执行 unlink某些链接可能是 MySQL 启动时依赖的正常结构比如 5.7 外部表空间的 .ibd 链接直接删会导致文件失踪。更安全的兜底方案是先对涉及链接的库表做一次 mysqldump 全量备份再用重建表的方式恢复。虽然耗时但免去了手动复制和权限不对的风险。按我的习惯链接数量少就手工处理链接数量多就导数据重建绝不在没备份的情况下批量 ln/unlink。说到底清理符号链接的目的是可控不是图省事。我现在把 symbolic_links 变量直接写进监控采集项新装实例的配置模板里也固定带 symbolic-links0从源头不给这种隐患留位置。那次应急响应之后再回访发现这类问题基本都是配置模板和管理制度的问题很少有业务真的需要符号链接。所以你如果也在做 MySQL 加固不用纠结先查状态、再清链接、最后关开关三步走完能少掉一个潜在风险点。