高危系统变量包括@@secure_file_priv(空字符串表示任意路径可读写)、@@local_infile(为1时客户端可能被诱导执行load data local infile)、@@datadir、@@plugin_dir、@@tmpdir(暴露物理路径助提权)及@@log_error等日志路径变量。

低权限用户默认就能读取大多数系统变量,这不是漏洞,是MySQL设计如此;真正要防的不是“读变量”,而是“通过变量泄露敏感信息”或“配合其他权限提权”。
哪些系统变量暴露后风险最高
用户执行 SELECT @@version、SELECT @@secure_file_priv 或 SHOW VARIABLES 时,能直接看到服务端配置。高危项包括:
-
@@secure_file_priv:返回NULL表示禁用文件操作,返回空字符串''表示任意路径可读写——攻击者拿到这个值,就知道能否用LOAD DATA INFILE读/etc/passwd -
@@local_infile:返回1意味着客户端可能被诱导发起LOAD DATA LOCAL INFILE,即使服务端已关,仍可能被 SQL 注入利用 -
@@datadir、@@plugin_dir、@@tmpdir:暴露物理路径,便于构造文件写入或 UDF 提权 -
@@log_error、@@slow_query_log_file:若日志路径可写,可能被覆盖或注入
不能靠权限回收来禁用变量读取
MySQL 没有「禁止读系统变量」的权限粒度。SELECT 权限本身就允许读 @@xxx 和 SHOW VARIABLES,哪怕你 REVOKE ALL PRIVILEGES ON *.*,只要用户能连上(即拥有 USAGE),就能执行这些语句。
常见误操作:
- 试图
REVOKE SELECT ON mysql.* FROM 'app'@'%'—— 没用,系统变量不走表权限校验 - 给用户授
USAGE后以为“零权限就安全了” —— 实际仍可查@@version、@@sql_mode等所有全局/会话变量 - 依赖
skip_show_database或--disable-show-databases—— 这只影响SHOW DATABASES,对SELECT @@xxx完全无效
真正有效的缓解手段只有三类
核心思路:不让变量值本身成为攻击跳板。
-
设
secure_file_priv = NULL:这是最硬核的一招。它让LOAD DATA INFILE和SELECT INTO OUTFILE直接失效,哪怕攻击者知道路径也没用。必须写进my.cnf的[mysqld]段并重启,SET GLOBAL不生效 -
关死
local_infile = OFF+ 客户端同步禁用:服务端设local_infile = OFF仅防服务端响应,但攻击链关键在客户端主动读本地文件。Python 的pymysql.connect(local_infile=False)、PHP 的mysqli_options($link, MYSQLI_OPT_LOCAL_INFILE, false)都得显式关 -
避免在错误提示里回显变量值:比如应用代码捕获异常后把
mysql_error()或完整 SQL 日志打到前端,可能意外泄露@@basedir或@@socket。应统一返回模糊提示,日志落盘即可
别忽略连接池和长连接带来的变量缓存问题
系统变量读取结果本身不缓存,但应用层常把 SELECT @@version 结果缓存在连接池元数据里。如果某次连接初始化时查到的是旧值(比如 secure_file_priv 还没设为 NULL),后续复用该连接的请求仍可能拿到过期信息——这不危险,但会让安全审计误判。
更麻烦的是:某些 ORM 或中间件(如 ProxySQL)会在启动时读一次 @@read_only 并长期缓存,导致主从切换后状态不同步。验证方式很简单:SELECT @@secure_file_priv; 和 SELECT @@local_infile; 必须在每个新连接里重查,不能信缓存。











