Server-side attacks and the file system
Log in Join

Lesson 3 · Reading · 35 min

File upload to code execution

Upload features are everywhere, avatars, attachments, document imports, and they are one of the most direct routes to running code on a server. The idea is simple: get a file that the server will execute into a place where it can be executed, then request it. This lesson is file upload to remote code execution.

Upload to RCE: get a script into a place the server runs, then request it

The two questions

Every upload-to-RCE hangs on two questions, and you need yes to both:

  1. Can I upload a file containing code the server will run? (A .php, .jsp, .aspx, or similar, matching the server's stack, which recon told you.)
  2. Can I reach a URL that causes the server to execute it? (Is the upload stored under the web root, and served in a way that runs it rather than downloading it as text?)

If yes to both, you upload a tiny script, a "web shell", that runs commands, request its URL, and you have code execution.

The defences, and how they are bypassed

Developers know uploads are dangerous, so they add checks. Each check has classic bypasses, and learning them is the lesson:

Extension allow/deny lists. The app blocks .php but the check is often weak:

  • Double extensions: shell.php.jpg or shell.jpg.php: depending on how the server maps extensions, one part still triggers execution.
  • Alternate executable extensions: .php5, .phtml, .pht, .phar for PHP; .jspx; case tricks like .PhP.
  • Trailing tricks: a trailing dot, space, or ::$DATA on some stacks, which the filter misses but the filesystem strips.

Content-Type checks. The app trusts the Content-Type header your client sends. You simply set it to image/jpeg while uploading your script, the header is yours to write, so this check is trivially bypassed in a proxy.

Magic-byte / content checks. The app inspects the file's start for an image signature. You prepend real image magic bytes (e.g. GIF89a;) before your code, making a polyglot that passes the "is it an image?" test while still executing as script. This is exactly the "polyglot" idea.

Image re-processing. Stronger apps re-encode uploaded images, which usually destroys embedded code. Then you look for other paths: metadata fields that survive processing, a different file type the feature also accepts, or a flaw in the processor itself.

.htaccess upload. If you can upload an .htaccess file, you may be able to tell the server to execute a new extension, turning an otherwise-inert upload into a runnable one.

Where the file lands matters as much as what it is

Getting the file uploaded is half of it; the other half is reachability. Find out where uploads go and whether that location executes code:

  • Note the URL of a normally-uploaded file (upload an image, see where it is served from).
  • Is that path under the web root and handled by the application server (so .php runs), or served as static/download-only (so it comes back as text)?
  • Some apps store uploads outside the web root or on separate storage precisely so they cannot be executed, a good defence. If your shell downloads as text instead of running, that is why, and you look for a different sink.

The web shell, kept minimal

A proof web shell can be a single line:

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

Request shell.php?c=id and the response contains the output of id, proving command execution. On a real engagement, id/whoami is the polite proof, it shows you can run commands without doing anything destructive.

The honesty gate

Upload-to-RCE is maximum impact and maximum responsibility. On a real target:

  • Prove with a read-only command (id, whoami, hostname), never anything that modifies or exfiltrates.
  • Delete your uploaded shell immediately after proving the finding; a live web shell left on a real server is a foothold you must not leave behind.
  • Do not pivot deeper (browse the file system, touch other hosts) beyond what the program's rules allow, proving execution is the finding.

On this Academy's disposable labs, run freely.

Your labs

PixelFrame gallery upload RCE is the classic: an image-upload feature you must get an executable file past, then reach to run it. Work through the bypasses above until your file both uploads and executes. VaultDocs export polyglot is the polyglot angle, crafting a file that satisfies a content/format check while still carrying code, the "looks like a document, runs like a script" trick. Do PixelFrame first for the mechanism, then VaultDocs for the polyglot craft.

Where this goes next

Uploading a script is one way to run code. The next lesson is the more direct one: features that hand your input straight to a shell command, where a well-placed ; or | runs your command alongside theirs, command injection.

This lesson has 3 labs to try what you just read.