Web Application Security

File Upload Vulnerabilities

Bypassing upload filters to plant a web shell - and how content-type, extension, and magic-byte checks fail.

Medium 20 minfile-uploadweb-shellrce

A file upload looks innocent - a profile picture, a CV, a CSV import. But if the server stores your file somewhere it will execute it, and it doesn't properly check what you uploaded, you can plant a web shell and run commands. Upload bugs are a fast track from "ordinary user" to full remote code execution.

Authorized targets only

Practice on your own lab (DVWA, Juice Shop, a VM you control). Uploading shells to systems you don't own is illegal - the goal here is to understand the failure so you can prevent it.

Why uploads are dangerous

Two things have to be true for an upload to become code execution:

  1. The server stores your file somewhere reachable (e.g. /uploads/).
  2. The server will execute files there (e.g. a PHP handler runs .php).

If both hold and the filter is weak, you win. So the defender's job is to break at least one of those.

The checks - and how each fails

1. Client-side validation (JavaScript)

The weakest "control" of all. If the browser blocks non-images before sending, just send the request directly with Burp Repeater or curl - the server never sees the JS check.

2. Extension blocklist

Blocking .php is a blocklist, and blocklists leak. Alternate executable extensions often still run:

.php5  .phtml  .pht  .phar     (PHP)
.asp  .aspx  .cshtml           (IIS / .NET)
.jsp  .jspx                    (Java)

Double extensions (shell.php.jpg, or shell.jpg.php) can slip past naive checks depending on how the server parses the name.

3. Content-Type header

Some servers trust the Content-Type you send. You control that header, so claim your PHP is an image:

curl -F 'file=@shell.php;type=image/png' http://TARGET/upload.php

4. Magic-byte / signature check

Servers that sniff the first bytes can be fooled with a polyglot: a file that starts with a valid image signature but also contains code.

kali@vr4cs: ~
 

The GIF89a header satisfies the signature check; the trailing PHP still executes.

Beyond web shells

Even when you can't get code execution, uploads enable other attacks:

  • Stored XSS via an SVG or HTML file that the browser renders.
  • Path traversal in the filename (../../etc/cron.d/evil) to write outside the upload dir.
  • DoS via huge files or decompression bombs.

Hands-on Lab

Upload Filter Bypass to Web Shell

Defeat a naive upload filter to plant a web shell and get command execution on a simulated server.

The fix: defense in depth

No single check is enough - stack them:

Secure upload design

  • Allowlist extensions and MIME types server-side (never blocklist).
  • Re-encode images through a trusted library so any embedded payload is destroyed.
  • Store uploads outside the web root, or in a directory where script execution is disabled.
  • Rename to a random UUID - never trust the user's filename.
  • Verify the real type with magic-byte inspection and the extension, and enforce a size limit.
  • Serve user files from a separate, sandboxed domain.

Key takeaways

  • An upload becomes RCE when the server both stores and executes your file and the filter is weak.
  • Client-side checks, extension blocklists, Content-Type, and magic bytes can each be bypassed individually.
  • The robust fix is layered: allowlist + re-encode + store outside web root + no execution in the upload directory + UUID filenames.