MySQL错误日志本身不记录SQL语句或参数,但敏感信息可能通过应用层错误堆栈、自定义函数报错、LOAD DATA失败路径或高权限用户动态配置等间接方式写入;必须从权限控制(如REVOKE FILE/PROCESS、禁super_priv)、配置收紧(log_error_verbosity=1或log_warnings=0)和日志路径隔离(属主mysql:mysql、权限600、非Web可访问路径)三方面综合防护。

MySQL 错误日志(error_log)本身不记录 SQL 语句、查询参数或用户数据,但敏感信息仍可能通过间接方式“绕道”写入——比如应用层错误堆栈、自定义函数抛出的含明文提示、LOAD DATA INFILE失败暴露的路径、或高权限用户动态修改日志行为。单纯指望 MySQL “自动过滤”敏感内容是无效的,必须从权限控制 + 配置收紧 + 日志路径隔离三方面动手。
为什么 error_log 里会出现敏感字段或路径
常见诱因不是 MySQL 主动记录,而是它成了“中转站”:
- 应用把
mysqli_error()或完整异常堆栈直接写进error_log(例如通过error_log($e->getMessage())) - 自定义存储函数用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = CONCAT('user:', @id),部分 MySQL 版本会将该消息落盘到error_log -
LOAD DATA INFILE '/tmp/user_12345.csv'失败时,错误日志里出现带用户名的路径 - 拥有
super_priv的用户执行SET GLOBAL log_output = 'TABLE'; SET GLOBAL general_log = ON;,再误把general_log当作error_log查看
必须撤销的三项关键权限
权限不是用来“禁止写日志”,而是砍掉容易触发敏感输出的操作源头:
- 执行
REVOKE FILE ON *.* FROM 'app_user'@'%';—— 防止LOAD DATA/SELECT ... INTO OUTFILE暴露临时路径或构造恶意报错 - 执行
REVOKE PROCESS ON *.* FROM 'app_user'@'%';—— 避免应用把SHOW PROCESSLIST结果当调试上下文打印,间接带出正在执行的 SQL - 确保普通用户无
super_priv:该权限允许动态改log_error_verbosity、log_output,甚至把慢查询日志重定向进error_log文件
my.cnf 中必须设置的两项核心配置
仅靠权限不够,配置必须同步收紧,否则运维误操作或低权限用户触发的 warning 仍可能进日志:
- MySQL 8.0.14+:在
[mysqld]段加log_error_verbosity = 1—— 只记录ERROR级别事件,跳过WARNING/NOTE,大幅减少因语法警告、连接拒绝等产生的冗余信息 - 旧版本(如 5.7):可用
log_warnings = 0,但注意该参数在 8.0 已废弃,混用会导致启动失败 - 绝对不要碰
sql_mode = STRICT_TRANS_TABLES或虚构的show_errors—— 前者只影响数据校验,后者根本不是合法配置项
日志文件本身的权限与路径隔离
即使内容干净,文件权限松动也会让攻击者直接读取历史日志:
- 确认
log_error路径(如/var/log/mysql/error.log)属主为mysql:mysql,权限设为600(chmod 600 /var/log/mysql/error.log) - 避免把
error_log放在 Web 可访问目录(如/var/www/html/logs/),也别和general_log、slow_query_log共用同一目录 - 检查是否启用了
log_error_services = 'log_filter_internal; log_sink_file'(8.0.28+ 默认值),不要手动改成log_sink_syseventlog或其他可能外泄的 sink
最容易被忽略的是:log_error_verbosity = 1 只控制 MySQL 自身日志粒度,对应用层写入的日志完全无效。如果 PHP 代码里有 error_log("password: " . $_POST['pwd']),这条明文照样会出现在 error.log 里——所以必须同时审计所有调用 error_log()、syslog()、trigger_error() 的地方,确保不拼接原始敏感字段。


















