php报错注入:原理、方法与防范
一、PHP报错注入的原理

PHP报错注入是一种利用数据库查询错误信息来获取敏感数据的攻击方式,当应用程序执行一个含有恶意SQL代码的查询时,如果查询出错,数据库系统通常会返回一个错误信息给应用程序,这个错误信息可能会包含有关数据库结构的敏感信息,如数据库名称、表名、列名等,攻击者可以利用这些信息来进一步构建更复杂的SQL注入攻击,以获取更多的数据库内容。
在PHP中,默认情况下,MySQL的报错信息并不会直接显示在页面上,通过使用mysql_error()
函数或类似的错误处理函数,可以捕获并显示这些错误信息,攻击者可以通过构造特定的输入,使得SQL查询语句产生错误,从而触发错误信息的显示,进而获取数据库的相关信息。
二、常见的PHP报错注入方法
(一)floor()报错注入
1、原理:通过concat()
函数连接注入语句与floor(rand(0)*2)
函数,实现将注入结果与报错信息回显的注入方式,在MySQL 8.0以下版本中有效,但在MySQL 8.0及以上版本中已失效。
2、示例:假设存在一个登录验证的SQL查询如下:
- $username = $_POST["username"];
- $password = $_POST["password"];
- $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
- $result = mysqli_query($conn, $sql);
- if (!$result) {
- echo mysqli_error($conn);
- }
攻击者可以在用户名输入框中输入:

- admin' union select count(*),1,concat((select user()),floor(rand(0)*2)) as a from information_schema.tables group by a #
这将导致SQL查询语句变为:
- SELECT * FROM users WHERE username='admin' union select count(*),1,concat((select user()),floor(rand(0)*2)) as a from information_schema.tables group by a #
由于floor(rand(0)*2)
的结果为0或1,会导致查询结果中的列数不一致,从而引发错误,错误信息中会包含当前连接的用户名。
(二)extractvalue()报错注入
1、原理:适用于MySQL 5.1.5+版本,通过在extractvalue
函数的第二个参数注入一个无效的Xpath表达式,导致函数报错,从而获取数据。
2、示例:对于上述登录验证的SQL查询,攻击者可以在用户名输入框中输入:
- admin' and (select extractvalue(rand(),concat(0x7e,(select user())))) #
这将导致SQL查询语句变为:

- SELECT * FROM users WHERE username='admin' and (select extractvalue(rand(),concat(0x7e,(select user()))))
由于extractvalue
函数的第二个参数不是有效的Xpath表达式,会引发错误,错误信息中会包含当前连接的用户名。
(三)updatexml()报错注入
1、原理:适用于MySQL 5.1.5+版本,通过在updatexml
函数的第二个参数注入不符合Xpath语法的表达式,从而引起数据库报错,并通过错误信息获取数据。
2、示例:攻击者可以在用户名输入框中输入:
- admin' and updatexml('x',concat('~',(select user()),'~'),'x') #
这将导致SQL查询语句变为:
- SELECT * FROM users WHERE username='admin' and updatexml('x',concat('~',(select user()),'~'),'x')
由于updatexml
函数的第二个参数不是有效的Xpath表达式,会引发错误,错误信息中会包含当前连接的用户名。
(四)其他报错注入方法
除了上述常见的报错函数外,还有其他一些函数(如exp()
、join
等)也可以用于报错注入,这些函数在特定条件下可以触发数据库错误,从而泄露敏感信息。
三、PHP报错注入的实例分析
以一个简单的用户登录功能为例,其PHP代码如下:
- $username = $_POST["username"];
- $password = $_POST["password"];
- $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
- $result = mysqli_query($conn, $sql);
- if ($row = mysqli_fetch_array($result)) {
- echo "Login successful!";
- } else {
- echo "Invalid username or password.";
- }
(一)正常情况
用户输入正确的用户名和密码,程序将查询数据库并返回相应的结果,如果用户名和密码匹配,则显示“Login successful!”;否则,显示“Invalid username or password.”。
(二)报错注入攻击
攻击者在用户名输入框中输入:
- admin' and updatexml('x',concat('~',(select user()),'~'),'x')
SQL查询语句将变为:
- SELECT * FROM users WHERE username='admin' and updatexml('x',concat('~',(select user()),'~'),'x') AND password='<user_input_password>'
由于updatexml
函数的第二个参数不是有效的Xpath表达式,会引发错误,如果程序中开启了错误显示,将会显示类似“XPATH syntax error: '~root@localhost~'”的错误信息,其中包含了当前连接的用户名“root@localhost”,攻击者可以根据这个错误信息进一步了解数据库的相关信息。
四、如何防范PHP报错注入
(一)关闭错误回显
在生产环境中,应关闭PHP的错误回显功能,可以通过修改PHP配置文件php.ini
,将display_errors
设置为Off
,或者在代码中使用error_reporting(0)
函数来关闭错误报告,这样,即使发生了SQL注入错误,也不会将错误信息显示给用户。
(二)使用预处理语句
使用预处理语句可以有效地防止SQL注入攻击,预处理语句会将SQL代码和数据进行分离,避免了直接拼接SQL字符串可能导致的安全风险,使用PDO或mysqli的预处理语句来执行SQL查询:
- $stmt = $pdo>prepare("SELECT * FROM users WHERE username=? AND password=?");
- $stmt>execute([$username, $password]);
- $row = $stmt>fetch();
- if ($row) {
- echo "Login successful!";
- } else {
- echo "Invalid username or password.";
- }
在上述代码中,?
是占位符,execute
方法会自动将参数绑定到对应的占位符上,避免了SQL注入的发生。
(三)过滤用户输入
对用户输入的数据进行严格的过滤和验证,确保输入的数据符合预期的格式和范围,可以使用正则表达式、字符串函数等方法对输入数据进行过滤,去除或转义可能引起SQL注入的特殊字符。
- $username = preg_replace("/[^azAZ09]/", "", $_POST["username"]);
- $password = preg_replace("/[^azAZ09]/", "", $_POST["password"]);
上述代码将用户名和密码中的非字母数字字符全部去除,减少了SQL注入的可能性。
(四)限制数据库权限
为应用程序所使用的数据库用户分配最低限度的权限,只允许该用户执行必要的操作,如查询特定表的数据等,避免使用具有过多权限的用户,如root
用户,以减少因SQL注入导致的数据泄露风险。
(五)定期更新和维护
及时更新PHP和数据库的版本,以修复已知的安全漏洞,对应用程序的代码进行定期审查和安全测试,及时发现并修复可能存在的安全隐患。