报错注入更危险是因为攻击者能直接看到数据库错误信息,如MySQL Error 1064或ORA-00904,暴露字段名、表结构及版本;其典型场景是未关闭错误显示、未用预处理且直接拼接用户输入,修复需禁用display_errors、统一try/catch捕获异常并仅记录日志、严格校验ORDER BY等动态子句、全面覆盖边缘路径。

报错注入为什么比普通注入更危险
因为攻击者能直接看到数据库错误信息,比如 MySQL Error 1064: You have an error in your SQL syntax 或 ORA-00904: invalid identifier,这些信息暴露了字段名、表结构甚至数据库版本。一旦开启 display_errors = On 或没捕获异常就输出 $e->getMessage(),等于把数据库的“体检报告”贴在首页上。
PHP 中 mysqli / PDO 报错注入的典型触发点
常见于未关闭错误显示 + 未使用预处理 + 直接拼接用户输入的组合场景。比如搜索页用 $_GET['q'] 拼进 WHERE title LIKE '%{$q}%',又没做 try/catch,攻击者输 ' and extractvalue(1,concat(0x3a,(select database())))-- 就能回显库名。
- 必须禁用生产环境的错误回显:
ini_set('display_errors', '0'),且确认error_reporting不含E_WARNING或E_NOTICE - 所有数据库操作必须包裹在
try/catch块中,异常只记录日志,绝不echo $e->getMessage() - 若用
PDO,初始化时明确关闭模拟预处理:new PDO($dsn, $user, $pass, [PDO::ATTR_EMULATE_PREPARES => false]),否则prepare()可能退化为字符串拼接 -
mysqli下避免混用面向对象和过程式写法,尤其别在mysqli_query()后直接用mysqli_error()输出——这是报错注入的黄金通道
修复时最容易被忽略的三个位置
补丁常只修显眼的登录/搜索入口,但报错注入真正藏在边缘路径里:分页参数、排序字段、导出条件、自定义查询接口。这些地方往往用 $_GET['order'] 或 $_POST['limit'] 直接拼进 SQL,而开发者认为“只是数字或字段名,不会出事”。
-
ORDER BY子句不能用预处理占位符,必须白名单校验:in_array($_GET['sort'], ['id', 'name', 'created_at'], true),否则?sort=id; DROP TABLE users--仍可执行 - 动态表名/字段名拼接前,必须严格限定范围,例如用
array_flip(['users' => 1, 'posts' => 1])做键值反转校验,而不是简单preg_match('/^[a-z_]+$/i', $table) - JSON API 接口里解析
$_POST['filter']后拼 SQL,要警惕嵌套数组被递归展开成恶意条件——这类逻辑往往绕过常规的intval()或htmlspecialchars()过滤
验证是否真修复了报错注入
别信扫描工具报“已修复”,得自己用典型 payload 验证。重点不是看页面是否报错,而是看错误信息里是否还含数据库细节。
- 输
' and 1=2 union select 1,2,3--,响应体里不该出现Unknown column '2' in 'field list'这类提示 - 输
1' OR SLEEP(3)--,响应时间不应明显变长(说明没触发数据库级延时) - 检查 Nginx/Apache 日志,确认所有 500 错误都只记
SQL execution failed,不带原始 SQL 或错误详情
报错注入的修复难点不在语法,而在所有可能拼接 SQL 的分支路径都要统一收口——漏掉一个 ORDER BY 校验或一处 error_log() 泄露,整站就还在裸奔。

















