关掉数据库错误信息的详细输出是防御报错型SQL注入最直接有效的动作;需同步关闭PHP display_errors、MySQLi/PDO驱动错误透出及确保错误仅写入安全日志,三者缺一不可。

关掉数据库错误信息的详细输出,是防御报错型 SQL 注入最直接有效的动作。只要错误里不再出现 Unknown column 'users.password'、updatexml() 报错回显、或 information_schema.tables 这类字段/表名,90% 以上的报错注入就失去探测能力。
PHP + MySQLi/PDO 必须关闭的三层错误透出
只设 display_errors = Off 不够,攻击者仍可能从响应体、日志或异常堆栈里拿到敏感信息:
-
mysqli_report(MYSQLI_REPORT_OFF)—— MySQLi 驱动默认可能开启MYSQLI_REPORT_STRICT,会让每个警告都抛致命异常并带原始 SQL,必须显式关掉 -
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT)——PDO::ERRMODE_EXCEPTION虽方便调试,但上线后等于把错误全暴露给 try/catch 外层;设为SILENT后需手动检查$stmt->errorCode() -
log_errors = On且error_log = /var/log/php/error.log—— 错误得写进日志供排查,但日志路径不能被 Web 目录直访,也不能在响应中 echo 出来
MySQL 服务端配置别碰错地方
MySQL 本身没有“禁止向客户端返回错误详情”的开关,它只按协议发错误码和简短消息。真正要调的是日志粒度和驱动行为:
- MySQL 8.0.14+:在
my.cnf的[mysqld]段加log_error_verbosity = 1,只记 ERROR 级别,不记 WARNING/NOTE - 旧版本(如 5.7):可用
log_warnings = 0,但注意该参数在 8.0 已废弃,别混用 - 别动
sql_mode = STRICT_TRANS_TABLES—— 它影响数据校验,和错误是否回显完全无关 - 别搜
show_errors—— 这不是合法 MySQL 配置项,my.cnf 里写了也无效
框架和 Web 服务器常漏掉的出口
很多团队改了 PHP 配置,却忘了其他通道仍在泄露:
- Laravel 要确认
APP_DEBUG=false生效,且.env没被 Git 提交;Django 除了DEBUG=False,还必须填好ALLOWED_HOSTS,否则 500 仍会吐 traceback - Apache 响应头里有
Server: Apache/2.4.6 (CentOS)?加ServerSignature Off和ServerTokens Prod - Nginx 若配了
error_page 500 /50x.html,检查页面里有没有硬编码$status或$upstream_response_time—— 这些变量可能被注入利用 - 自定义错误页里绝不能拼接
$_SERVER['QUERY_STRING']或原始 SQL,这是二次泄露高发区
验证是否真关严了的实操方式
别只看配置文件改没改,要主动触发错误观察输出:
- 在登录框或 ID 参数处输入
1' AND (SELECT 1 FROM information_schema.tables) --,响应应是泛化提示(如“操作失败”),而非含information_schema的报错 - 访问一个明显语法错的查询,如
?id=1' ORDER BY 999,确认返回 HTTP 500 且 body 无字段名、表名、函数名 - 用 curl 检查响应头:
curl -I https://yoursite.com/api/user?id=1%27,确保没有X-Powered-By: PHP/8.2或Server泄露 - 查日志:
tail -f /var/log/mysql/error.log和/var/log/php/error.log,确认错误写入但未出现在 HTTP 响应中
最容易被忽略的一点:预处理语句(prepare/bind)防不住报错注入的触发逻辑。哪怕你用了 WHERE id = ?,只要 SQL 模板里自带 updatexml() 或 extractvalue(),错误照样回显。所以错误屏蔽和参数化必须同时做,缺一不可。











