必须关闭应用层错误信息泄露:MySQLi设mysqli_report为MYSQLI_REPORT_OFF,PDO设ATTR_ERRMODE为SILENT或WARNING,并禁用PHP的display_errors;Flask/Django上线务必关闭debug模式,同时避免日志记录敏感SQL。

PHP 中如何关闭 MySQLi/PDO 的详细错误提示
默认开启的详细错误信息会把数据库结构、SQL 语句甚至服务器路径直接吐给前端,这是 SQL 注入后最常被利用的突破口。关掉它不是“不让报错”,而是不让敏感信息泄露。
关键在两处:驱动层的错误报告级别 + PHP 自身的错误显示开关。
-
mysqli_report默认是MYSQLI_REPORT_OFF,但很多老项目手动设成了MYSQLI_REPORT_STRICT,一出错就抛异常并带完整 SQL —— 必须改成MYSQLI_REPORT_OFF或至少MYSQLI_REPORT_ERROR(不带 SQL) - PDO 要禁用
PDO::ATTR_ERRMODE的PDO::ERRMODE_EXCEPTION,改用PDO::ERRMODE_SILENT或PDO::ERRMODE_WARNING - 同时确保
display_errors = Off(php.ini)或运行时执行ini_set('display_errors', '0'),否则即使驱动不报,PHP 还是可能把错误打到页面上
示例(PDO):
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT, // 关键
PDO::ATTR_EMULATE_PREPARES => false,
]);
Python Flask/Django 中屏蔽数据库错误堆栈
Flask 默认调试模式(debug=True)下,500 错误会渲染完整 traceback,包含原始 SQL 和表名;Django 的 DEBUG=True 同理。这不是数据库配置问题,是框架层泄露。
- 上线必须关
debug=False(Flask)或DEBUG = False(Django settings.py) - Django 还要检查
ALLOWED_HOSTS是否配置正确,否则连 400 错误都可能暴露请求头和路径 - 即使关了 debug,如果用了
sqlalchemy,别忘了设echo=False(避免日志里打印 SQL),且不要在生产日志中记录engine.execute()的异常详情
常见错误现象:访问 /api/user?id=1%27%20UNION%20SELECT%20password%20FROM%20users-- 后,页面返回 “OperationalError: (1064, “You have an error in your SQL syntax…””,这就是典型没关框架级错误渲染。
MySQL 服务端要不要关 show_errors 或 log_warnings?
不用动这些。MySQL 本身没有“向客户端返回详细错误”的全局开关;它只按协议把错误码和简短消息发给客户端,真正泄露细节的是应用层把 mysql_error()、mysqli_error()、conn.error 这类函数结果直接 echo 或 print 出来。
-
log_warnings=2是服务端写错误日志用的,不影响客户端看到什么 -
show_errors不是 MySQL 配置项,是某些客户端工具(如 mysql CLI)的选项,和服务端无关 - 唯一要查的是应用代码里有没有类似
die(mysqli_error($conn))或raise Exception(str(e))把数据库异常原样抛出
为什么预处理语句不能代替关错误提示?
预处理(Prepared Statement)防的是 SQL 注入本身,关错误提示防的是“注入失败后还能偷看结构”。两者解决的是不同阶段的风险。
- 即使用了
prepare/execute,如果用户构造恶意参数让查询逻辑出错(比如传入超长字符串触发data too long),错误信息仍可能暴露字段长度、约束名甚至表名 - 有些 ORM(如旧版 SQLAlchemy)在 debug 模式下,会把绑定参数后的完整 SQL 写进日志或异常 message,这和是否预处理无关
- 真实案例:某系统用 PDO 预处理查用户,但错误处理写成
throw new Exception($e->getMessage()),攻击者通过报错里的 “Unknown column 'emailx' in 'field list'” 推出字段名拼写规则
最易被忽略的点:日志文件本身。就算页面不显示错误,如果 error_log 里记了完整异常堆栈,且日志目录能被 Web 直接访问(比如放在 webroot/logs/),那就等于把钥匙挂门口。

















