對一道ctf賽題的詳細分析

0x01 前言

最近身邊有萌新想打ctf,我作爲一個曾經接觸過一點點ctf的業餘菜雞,就索性做了一道Web題。這篇文章主要是面向想開始打ctf的萌新,所以很多地方可能都比較簡單。如有錯誤之處,歡迎各位指正。

0x02 Flask 簡介

​Flask庫是一個非常小型的Python Web開發框架。它有兩個主要依賴:路由、調試和 Web 服務器網關接口(Web Server Gateway Interface,WSGI)子系統由 Werkzeug(http://werkzeug.pocoo.org/)提供;模板系統由 Jinja2(http://jinja.pocoo.org/)提供。Werkzeug 和 Jinjia2 都是由 Flask 的核心開發者開發而成。這裏我們要重點了解一下路由。

Flask需要知道對每個 URL 請求該運行哪些代碼,所以保存了一個 URL 到Python 函數之間的映射關係。處理 URL 和函數之間關係的程序稱爲路由。在 Flask 程序中定義路由的最簡便方式,是使用程序實例提供的 app.route 修飾器,把修飾的函數註冊爲路由。下面的例子說明了如何使用這個修飾器聲明路由:

@app.route('/')
def index():
return '<h1>Hello World!</h1>'

這個例子就是把 index() 函數註冊爲程序根地址的處理程序。如果部署程序的服務器域名爲 www.example.com,在瀏覽器中訪問 http://www.example.com 後,會觸發服務器執行 index() 函數。這個函數的返回值稱爲響應,是客戶端接收到的內容。如果客戶端是 Web 瀏覽器,響應就是顯示給用戶查看的文檔。

要啓動服務器也很簡單,程序實例用 run 方法啓動 Flask 集成的開發 Web 服務器即可:

if __name__ == '__main__':
   app.run(debug=True)

其中,name=='main' 是 Python 的慣常用法,在這裏確保直接執行這個腳本時才啓動開發Web 服務器。服務器啓動後,會進入輪詢,等待並處理請求。輪詢會一直運行,直到程序停止,比如按Ctrl-C 鍵。有一些選項參數可被 app.run() 函數接受用於設置 Web 服務器的操作模式。在開發過程中啓用調試模式會帶來一些便利,比如說激活調試器和重載程序。要想啓用調試模式,我們可以把 debug 參數設爲 True。要想讓所有主機都可以訪問,可以設置參數host="0.0.0.0"。

0x03 詳細分析

賽題如下:

該Web題下載下來以後源碼如下:

import os
import json
from shutil import copyfile
from flask import Flask,request,render_template,url_for,send_from_directory,make_response,redirect
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import jsonify
from hashlib import md5
import signal
from http.server import HTTPServer, SimpleHTTPRequestHandler
​
os.environ['TEMP']='/dev/shm'
​
app = Flask("access")
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1 ,x_proto=1)
​
​
@app.route('/',methods=['POST', 'GET'])
def index():
   if request.method == 'POST':
       f=request.files['file']
       os.system("rm -rf /dev/shm/zip/media/*")
       path=os.path.join("/dev/shm/zip/media",'tmp.zip')
       f.save(path)
       os.system('timeout -k 1 3 unzip /dev/shm/zip/media/tmp.zip -d /dev/shm/zip/media/')
       os.system('rm /dev/shm/zip/media/tmp.zip')
       return redirect('/media/')
   response = render_template('index.html')
   return response
​
@app.route('/media/',methods=['GET'])
@app.route('/media',methods=['GET'])
@app.route('/media/<path>',methods=['GET'])
def media(path=""):
   npath=os.path.join("/dev/shm/zip/media",path)
   if not os.path.exists(npath):
       return make_response("404",404)
   if not os.path.isdir(npath):
       f=open(npath,'rb')
       response = make_response(f.read())
       response.headers['Content-Type'] = 'application/octet-stream'
       return response
   else:
       fn=os.listdir(npath)
       fn=[".."]+fn
       f=open("templates/template.html")
       x=f.read()
       f.close()
       ret="<h1>文件列表:</h1><br><hr>"
       for i in fn:
           tpath=os.path.join('/media/',path,i)
           ret+="<a href='"+tpath+"'>"+i+"</a><br>"
       x=x.replace("HTMLTEXT",ret)
       return x
​
​
os.system('mkdir /dev/shm/zip')
os.system('mkdir /dev/shm/zip/media')
app.run(host="0.0.0.0",port=8080,debug=False,threaded=True)
​

接下來就開始詳細介紹。

這是一個用來實現文件在線解壓的網站,首先看首頁對應的index函數,當訪問網站首頁時,可以採用GET和POST請求兩種方式,對應的響應函數爲index函數。題目裏還有兩個html文檔,一個用來當上傳文件時,一個用來渲染頁面,這裏就不貼出來了。當在首頁通過html上傳文檔時,會執行index函數,會執行如下命令來刪除文件:

rm -rf /dev/shm/zip/media/*

然後拼接路徑,並將上傳的文件保存到這個路徑:/dev/shm/zip/media/tmp.zip

之後執行命令解壓:

timeout -k 1 3 unzip /dev/shm/zip/media/tmp.zip -d /dev/shm/zip/media/

解壓後將壓縮文件刪除:

rm /dev/shm/zip/media/tmp.zip

第二個函數是media函數,有三種url請求都由這個函數來響應,其中有一個需要特別關注的,就是

@app.route('/media/<path>',methods=['GET'])

這是動態路由,就是當請求的時候如果在media的後面再加上一個字符串,可以將這個字符串作爲參數傳遞給path變量,執行media函數中的內容:

def media(path=""):
   npath=os.path.join("/dev/shm/zip/media",path)
   if not os.path.exists(npath):
       return make_response("404",404)
   if not os.path.isdir(npath):
       f=open(npath,'rb')
       response = make_response(f.read())
       response.headers['Content-Type'] = 'application/octet-stream'
       return response
   else:
       fn=os.listdir(npath)
       fn=[".."]+fn
       f=open("templates/template.html")
       x=f.read()
       f.close()
       ret="<h1>文件列表:</h1><br><hr>"
       for i in fn:
           tpath=os.path.join('/media/',path,i)
           ret+="<a href='"+tpath+"'>"+i+"</a><br>"
       x=x.replace("HTMLTEXT",ret)
       return x

仔細觀察可以看到,path作爲參數會被拼接到npath變量中,然後當npath不存在時,返回404;當npath不是目錄時,會使用open函數打開,並返回給請求者。題目中提到了flag位於根目錄,而path我們又可以控制,那麼我們只要能夠通過一種方式,將npath變成/flag是不是就可以了呢?心裏想,直接用相對路徑../實現路徑穿越不就妥了?這麼簡單的嗎?

0x04 峯迴路轉

說幹就幹,我把path設置成../../../../flag,直接在瀏覽器請求http://example.com/media/../../../../flag 結果發現不對,瀏覽器直接把我這些../全去掉了?url變成了http://example.com/media/flag,whatfuck? 第一反應是瀏覽器的問題,那就換個瀏覽器,結果發現也不行,然後想了想,那就用burp suite吧,結果還是不行。

最後,我在自己電腦上運行這個代碼,開始各種調試,最後發現把斜槓換成兩個反斜槓就可以了,能夠實現路徑穿越,讀取上一層的文件內容,可是換成題目中就是不行。我仔細想了想應該是因爲我本地是windows,而服務器是Linux,所以纔不行的。眼看着時間不早了該睡覺了,還是沒搞出來,於是喝了一杯茶,想了想,最後靈機一動,還是沒想出來怎麼搞。算了,睡覺吧。

到了第二天早上,我覺得這個必須得搞定。於是,我想到了Linux中的軟鏈接!突然一下子就明白該怎麼搞了!於是,打開我塵封已久的kali虛擬機,然後慢慢悠悠的敲下了如下命令:

成功創建了一個指向/flag的軟鏈接,但是這個軟鏈接怎麼利用呢?這個網站既然是用來在線解壓的,那我就把軟鏈接打成一個壓縮包傳上去,它直接就會被解壓到當前目錄。然後我直接點擊該文件,直接就下載下來一個車文件,打開即可看到flag內容爲:

flag{NeV3r_trUSt_Any_C0mpresSeD_file}

最後終於搞定了,這一刻還是很開心的。作爲一個業餘ctf菜雞選手,實在是不容易。還是要多做題,多開闊眼界,學習各種騷操作!

 

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