前几天帮一个朋友做 MySQL 安全加固扫了一圈发现他的 5.7 实例里/var/lib/mysql下躺着一个指向/etc/passwd的符号链接而my.cnf里没有显式的symbolic-links0。那一瞬间我后背发凉。后来确认那只是历史遗留文件没有被实际利用但这件事让我决定把“禁用 symbolic-links”这件事好好写一写。它不是什么新机制也不会出现在大多数 mysql 安装配置教程的检查清单里但它直接影响 MySQL 实例在操作系统层面的文件访问边界。这篇文章会把这一个参数讲透它是什么怎么关关了之后还要做什么以及我实操中踩过的坑。适合刚装完 MySQL 的新手也适合正在做安全加固的运维和 DBA。1. 为什么要盯住 symbolic-links 这个参数1.1 符号链接在 MySQL 中原本是干什么用的符号链接这东西玩 Linux 的人应该不陌生。ln -s /data/other_disk/table.MYD /var/lib/mysql/db/table.MYD一执行就能把一个文件“映射”到另一个目录下应用层几乎感知不到区别。你可以把它理解成一个快捷方式但比 Windows 快捷方式更底层因为内核在打开文件时会直接顺着这个链接找到真实文件。MySQL 早期支持符号链接主要目的是方便做冷数据分离和跨磁盘存储。比如 MyISAM 表它的数据文件是.MYD索引文件是.MYI表结构是.frm老版本。如果一个库越来越大单块磁盘不够用管理员可以把某个 MyISAM 表的数据文件挪到另一块挂载盘再在原位置建个符号链接指过去。这样 MySQL 进程认为表文件还在数据目录里实际物理文件却已经搬到别的地方去了。这种玩法在机械硬盘时代确实解决过不少 IO 压力问题所以my.cnf里才有了symbolic-links这个开关1表示允许0表示禁用。但今时不同往日。InnoDB 成为默认存储引擎后表级文件符号链接的使用场景越来越少。InnoDB 使用表空间管理数据官方并不鼓励对单个 InnoDB 表做这种 OS 层面的链接。很多时候历史配置文件里保留的symbolic-links1并不是业务需要而是当年安装时顺手带过来的。安全加固最重要的一步就是把这种“看起来无关紧要真出事就是大问题”的开关显式关掉。1.2 真正的风险越权的文件读写路径很多人第一次听说这个参数时会问符号链接而已能有什么风险关键在于 MySQL 进程在操作系统里的身份。MySQL 服务通常以mysql系统用户运行。这个用户不是 root但也不是完全没有权限。它至少能读写自己的数据目录、日志目录、临时目录等。假设攻击者已经拿到了数据目录内的写权限比如利用文件上传漏洞、SQL 注入配合 OUTFILE 写入、或者某个被入侵的定时任务获得了部分控制权那他很可能做的事就是往数据目录里塞一个符号链接。最常见的攻击思路是把某个 MyISAM 表的数据文件替换成指向敏感文件的符号链接。比如# 假设攻击者已经能进入 /var/lib/mysql/testdb/ rm -f /var/lib/mysql/testdb/t1.MYD ln -s /etc/passwd /var/lib/mysql/testdb/t1.MYD如果symbolic-links1MySQL 打开t1这张表时内核会顺着链接去读/etc/passwd表里的内容就会变成系统用户文件的内容。反过来如果业务对这个表有写操作MySQL 还可能以mysql用户的身份去修改被链接的目标文件。这个目标文件可以是另一个应用目录下的配置文件也可以是其他服务有写权限但有漏洞的文件权限边界瞬间被放大。更麻烦的是组合攻击。如果某个数据库账号被授予了FILE权限攻击者还可以通过LOAD DATA INFILE读取文件、通过SELECT INTO OUTFILE写文件。这时再配合一个符号链接数据库就变成了攻击者读写服务器文件的“中转站”。禁用symbolic-links不能消除所有文件读写风险但能把其中一条非常隐蔽的越权路径直接切断。1.3 版本差异与默认值陷阱关于这个参数网上最容易误导人的就是“默认值”。我在不同 Linux 发行版上见过完全不同的现状有些 RPM 包默认生成的my.cnf里就有symbolic-links0有些源码编译的实例配置里什么都没写还有些老实例升级上来后配置文件里还保留着古老的symbolic-links1。MySQL 5.6、5.7 时代symbolic-links是一个很明确的服务器选项可以用SHOW VARIABLES查看到。到了 MySQL 8.xInnoDB 和数据字典的管理方式变化很大部分版本的二进制在 SQL 层已经不再暴露symbolic_links这个变量但这不代表历史上遗留的配置不会产生影响。很多容器镜像、第三方发行版、旧版升级上来的实例仍然可能读取这个参数。我的建议是不要赌默认值不要依赖某个发行版的打包习惯。无论你用的是官方二进制、RPM、源码编译还是 Docker 镜像都在[mysqld]下显式写上symbolic-links0。写清楚之后至少排除了一个不确定因素。2. 禁用 symbolic-links 的完整操作2.1 找到正在使用的配置文件改配置前先别急着打开/etc/my.cnf因为不同系统、不同安装方式的配置文件位置差别很大。最笨的办法是到处find但我更推荐直接问 MySQL 自己。执行这个命令可以看到 mysqld 启动时按什么顺序读取配置文件mysqld --verbose --help 2/dev/null | grep -A1 Default options输出通常会给出类似这样的顺序/etc/my.cnf /etc/mysql/my.cnf ~/.my.cnf常见发行版和安装方式的对应关系我也整理过环境主要配置文件RHEL / CentOS / Fedora/etc/my.cnfDebian / Ubuntu/etc/mysql/mysql.conf.d/mysqld.cnf或/etc/mysql/my.cnf源码编译安装/etc/my.cnf或$basedir/my.cnfDocker 官方镜像容器内/etc/my.cnf自定义配置建议挂载到/etc/mysql/conf.d/K8s / KubeSphere 部署通过 ConfigMap 挂载到/etc/mysql/conf.d/下的某个.cnf文件这里有一个经验不要只盯着/etc/my.cnf看。Debian 系的配置经常用!includedir /etc/mysql/conf.d/这种语法引入一堆子配置真正的生效参数可能藏在某个子文件里。只看一个文件很容易漏判。2.2 修改配置不是加一行那么简单找到配置文件后先备份再修改cp /etc/my.cnf /etc/my.cnf.bak.$(date %F)打开文件找到[mysqld]这个组。注意是[mysqld]不是[mysql]也不是[mysqld_safe]。很多新人会把参数写到[mysql]下面那是客户端工具读的配置对服务器完全不生效。在[mysqld]下面加一行[mysqld] symbolic-links0如果文件里已经有symbolic-links1一定要改成0不要重复出现两个值。如果使用了多个配置文件或者有!includedir引入的子配置还要把其他文件里可能存在的symbolic-links也检查一遍。多个配置同时出现时后面的文件可能覆盖前面的设置所以只改一个文件而不看全局很容易出现“配了又好像没配”的错觉。配置文件的权限也要注意。mysqld 通常不允许加载 world-writable 的配置文件否则会在日志里提示World-writable config file is ignored。修改完成后设置一下chown root:root /etc/my.cnf chmod 644 /etc/my.cnf改完先别急着重启可以用my_print_defaults确认 mysqld 最终能读到的参数my_print_defaults mysqld | grep symbolic正常输出应该是--symbolic-links0如果这里输出的是--symbolic-links1说明还有别的配置文件在覆盖需要继续排查。如果什么都没输出说明当前配置文件里没有显式声明这个参数想办法补上。2.3 重启并确认参数真实生效配置生效需要重启 mysqld。不同环境重启方式不一样systemctl restart mysqld # 或者老系统用 service mysql restart # 源码编译安装的进程可以用 mysqladmin -u root -p shutdown mysqld_safe --usermysql 重启后先确认服务状态systemctl status mysqld --no-pager -l然后登录 MySQL 检查变量mysql -uroot -p -e SHOW VARIABLES LIKE %symbolic%;在 MySQL 5.7 或类似版本中你可能会看到symbolic_links对应的值是OFF这个状态就是正确的。SQL 层变量名用的是下划线命令行参数用的是连字符这是 MySQL 的常规命名习惯别把这两个弄混。如果你的 MySQL 8.x 版本执行这条命令返回空集不用太紧张这大概率是版本行为差异。此时最可靠的办法还是看my_print_defaults的输出以及启动日志。无论如何最终目标只有一个配置文件里显式是 0服务启动日志里没有符号链接相关的告警。3. 只改参数还不够配套加固动作要做齐3.1 数据目录归属和权限symbolic-links0只是关掉了 MySQL 自己的符号链接支持。但操作系统层面如果数据目录本身就是一个符号链接或者数据目录的父目录权限过于宽松攻击者还是可以做文章。先检查数据目录是不是符号链接stat -c %F %U:%G %a %N /var/lib/mysql正常的输出应该类似directory mysql:mysql 700 /var/lib/mysql如果第一列是symbolic link那就要追查它指向哪里并确认目标目录的权限是否合理。数据目录本身的归属最好是mysql:mysql权限建议700或750不要用777。父目录的权限同样重要。如果/var/lib或/var/lib/mysql的父目录是 world-writable攻击者可能把整个数据目录替换成符号链接那不管 MySQL 内部怎么设置都挡不住。这个属于主机层面的安全基线和数据库参数要一起检查。3.2 secure_file_priv 对导入导出类攻击的限制禁掉符号链接后文件读写还有另一条路LOAD DATA INFILE和SELECT INTO OUTFILE。这两个 SQL 语句直接操作服务器文件系统非常危险。MySQL 提供了secure_file_priv参数来限制导入导出路径。登录 MySQL 查看当前设置SHOW VARIABLES LIKE secure_file_priv;比较安全的做法是把它指向一个专用的临时目录比如[mysqld] secure-file-priv/var/lib/mysql-files创建目录并设置权限mkdir -p /var/lib/mysql-files chown mysql:mysql /var/lib/mysql-files chmod 700 /var/lib/mysql-files注意secure_file_priv是只读系统变量改配置后同样需要重启 MySQL 才能生效。如果这个变量的值是空字符串代表路径不受限制这是比较危险的如果是NULL代表禁用导入导出功能。生产环境按业务需求选择合适策略不要为了方便把所有限制全部放开。3.3 巡检建议周期性检查符号链接安全习惯比一次加固更重要。我在维护的实例上会加一条每日巡检脚本专门扫描数据目录下有没有符号链接。脚本很简单#!/bin/bash DIR/var/lib/mysql REPORT/var/log/mysql_symlink_report.$(date %F).log find $DIR -type l -printf %p - %l\n $REPORT if [ -s $REPORT ]; then cat $REPORT # 这里可以接上告警系统比如发送到监控平台 else echo No symbolic link under $DIR fi放到 crontab 里每天执行一次0 4 * * * /usr/local/sbin/check_mysql_symlink.sh除了文件系统巡检还要定期检查数据库账号的FILE权限。执行这条 SQLSELECT User,Host,File_priv FROM mysql.user WHERE File_privY;如果发现除了管理员账号之外还有业务账号带FILE权限要立刻确认是不是误授。FILE权限配合文件路径操作是符号链接攻击最常见的帮凶。3.4 MyISAM 表与符号链接的历史遗留如果你维护的库还在用 MyISAM那符号链接的隐患会比 InnoDB 大得多。查一下当前实例里还有多少 MyISAM 表SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE FROM information_schema.tables WHERE ENGINE MyISAM;如果查询结果不为空建议尽快评估迁移到 InnoDB。MyISAM 不支持行级锁、崩溃恢复能力弱又容易成为符号链接攻击的载体。转换方式很简单ALTER TABLE testdb.t1 ENGINEInnoDB;但线上大表转换前一定要先做备份转换期间可能锁表要安排在业务低峰期。如果业务真的需要跨磁盘存放某些数据不要再用“建符号链接”这种老办法。现代 Linux 下可以直接挂载独立磁盘到专门目录或者用 InnoDB 的独立表空间来管理存储位置安全性好得多。4. 实战里容易踩的坑和排查思路4.1 改完配置后 MySQL 起不来这是我被问得最多的一种情况。明明只是在[mysqld]下加了一行symbolic-links0重启后服务却起不来了。遇到这种情况第一步不是反复重启而是看日志。RHEL 系列一般在/var/log/mysqld.logDebian/Ubuntu 一般在/var/log/mysql/error.log。如果用的 systemd还可以直接看 journaljournalctl -u mysqld -n 100 --no-pager常见的坑有几个一是配置文件语法错误比如少了组名或者选项后面多了空白字符二是配置文件权限不对mysqld 拒绝读取三是某些特别精简的 MySQL 编译版本根本不认symbolic-links这个参数启动时报unknown variable。最后一种情况不需要硬刚删掉这行配置在操作系统层面保证数据目录没有符号链接、权限收紧同样能达到防护目的。还有一种情况你用的是 MySQL 8.x但配置里同时保留了一些早已不兼容的旧参数。这时候要注意不一定是symbolic-links本身的问题可能是其他参数连坐导致启动失败。仔细看日志逐条排查别把锅全甩到一个参数上。4.2 配置文件读取顺序造成的“配了没生效”有次我帮人排查他明确在/etc/my.cnf里写了symbolic-links0但my_print_defaults mysqld输出的还是--symbolic-links1。最后发现原因很简单他用的 Debian 系统/etc/my.cnf里有一行!includedir /etc/mysql/conf.d/而/etc/mysql/conf.d/下还留着一个旧配置里面写着symbolic-links1。后面读取的文件覆盖了前面的设置所以他的修改根本没生效。排查这类问题命令很直接grep -rn symbolic-links /etc/my.cnf /etc/mysql/把每个配置文件、每个子目录都扫一遍确保只有一个地方定义了这个参数。如果项目里有多套配置建议把安全参数统一收敛到一个专门的文件里比如/etc/my.cnf.d/security.cnf这样后续审计也方便。4.3 客户端报 error 2002 的连接问题改完配置后经常有人急匆匆跑过来说“MySQL 连不上了”错误信息是ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock (2)这个报错的意思是客户端通过/tmp/mysql.sock这个 UNIX socket 找不到 MySQL 服务。大多数情况下这不是symbolic-links导致的而是服务根本没启动成功或者服务已经启动但 socket 路径不对。先确认服务状态如果状态是failed回到 4.1 去看错误日志。如果服务正常再检查 socket 路径SHOW VARIABLES LIKE socket;看看 MySQL 实际监听的 socket 和客户端连接时用的路径是否一致。不一致就在客户端命令里加-S /var/lib/mysql/mysql.sock或者在my.cnf的[client]段里统一配置 socket 路径。有一点要特别记住symbolic-links0不会改变 socket 路径、端口号或网络监听地址。如果只是加了这个参数就出现连接问题优先怀疑服务和配置合并问题不要在无关参数上浪费时间。4.4 Docker 与云数据库场景怎么处理Docker 部署 MySQL 时容器内的/etc是临时文件系统直接docker exec进去改配置重启容器之后就会丢失。正确做法是把自定义配置挂载进去。创建一个配置文件/data/mysql/security.cnf[mysqld] symbolic-links0 secure-file-priv/var/lib/mysql-files然后启动容器docker run -d --name mysql-security \ -v /data/mysql/security.cnf:/etc/mysql/conf.d/security.cnf:ro \ -e MYSQL_ROOT_PASSWORD你的密码 \ mysql:5.7在 Kubernetes 或 KubeSphere 这类云原生环境里也一样把配置通过 ConfigMap 挂载到容器的/etc/mysql/conf.d/下而不是改造镜像内部文件。镜像每次重建、扩容时配置都能保持一致不会出现“某个节点的 MySQL 配置和其他节点不一样”的诡异问题。云数据库是另一类情况。如果你用的是托管 RDS 服务服务器端由厂商管理通常不会开放symbolic-links这种底层参数给用户。遇到这种情况不要试图通过 SSH 或者奇怪手段去改托管服务天然隔离了操作系统层符号链接风险你只需要确认账号权限和secure_file_priv这类可以在控制台调的参数即可。自建数据库和托管数据库的安全责任边界完全不一样脑子里要分清楚。5. 我最后的加固心得做了这么多年 MySQL 运维我越来越觉得安全加固不是某个单点操作而是一套习惯。symbolic-links0本身只是一个小参数但它背后代表的是对文件系统边界的关注。我现在每接手一个新实例不管它是源码编译的、RPM 装的还是 Docker 起的都先按照这个顺序检查先跑my_print_defaults mysqld看真实生效配置再查[mysqld]下有没有显式的symbolic-links0然后看数据目录是不是符号链接、权限是不是正常最后扫一遍有没有FILE权限账号和文件系统里的异常链接。这套动作做完心里才有底。还有一点想提醒大家不要在没必要的时候为了那一点“历史兼容性”把安全参数打开。MyISAM 跨盘存储的老需求今天已经有更安全、更可控的替代方案符号链接这种手段早该退居幕后了。安全加固这种工作最怕的就是“觉得默认没问题”。把所有关键参数显式化、可审计化是我这几年踩过不少坑之后最深的体会。
