Authorised testing only: All demonstrations in this lesson use DVWA in your isolated lab environment. Web shells are powerful tools — uploading one to any system you do not own or have explicit written permission to test is illegal and constitutes unauthorised computer access. In professional assessments, web shell uploads are performed only within documented scope and cleaned up immediately after demonstration.
18
Almost every web application has a feature that accepts files from users — profile photos, document uploads, attachment submissions. These features seem innocuous. But when the application fails to properly validate what’s being uploaded, that feature becomes a direct path to executing arbitrary code on the server. The upload dialog is just a door. The question is what walks through it.
File upload vulnerabilities consistently lead to Remote Code Execution — the highest severity finding in any security assessment. They appear in bug bounty programmes, professional penetration tests, and real-world breaches with alarming regularity. Today you’ll understand exactly how they work, why common defences fail, and how to implement upload handling that actually holds.
What makes file upload vulnerabilities particularly impactful is their end goal: if you can upload a server-side script and execute it, you have achieved Remote Code Execution. Not just data exposure — actual command execution on the target server with the web server process’s permissions. From there, the path to full server compromise is usually straightforward.
The vulnerability chain has three steps. Each step must succeed for the attack to work — and each one represents a potential defensive checkpoint that, if properly implemented, breaks the chain.
STEP 1 — UPLOAD BYPASS
The attacker must upload a file containing server-executable code — typically a PHP script. The application’s upload validation must be absent or bypassable. If the server checks the file type, the attacker must fool that check.
STEP 2 — FILE STORAGE IN WEB ROOT
The uploaded file must be stored in a web-accessible location — a directory served by the web server. Files uploaded to storage buckets, databases, or directories outside the web root cannot be executed via the browser.
STEP 3 — EXECUTION PERMITTED
The web server must be configured to execute scripts in the upload directory. If PHP execution is disabled in that directory (via .htaccess or server config), the file is served as plaintext rather than executed.
Secure upload implementations break all three steps simultaneously. Vulnerable ones fail at step one — and the rest follows automatically.
What Is a Web Shell?
A web shell is a script — most commonly PHP, but also ASP, JSP, Python, or any server-side language — that accepts commands via HTTP parameters and executes them on the server, returning the output in the response. The attacker interacts with the server through their browser rather than through a traditional terminal connection.
Web shell anatomy — from minimal to functional (lab use only)
💡 Why this is critical severity: The web server process (www-data on Linux) typically has read access to the entire web root including configuration files with database credentials, write access to upload directories, and often the ability to read other application files on the same server. From a web shell, an attacker can frequently pivot to full database access and potentially other hosted applications.
LEVEL 1
DVWA Low Security — No Validation at All
At DVWA’s Low security level, the file upload feature has effectively no validation. It accepts any file type and stores it directly in a web-accessible uploads directory. This represents the worst possible implementation — and it’s more common in real applications than you might expect, particularly in internal tools and legacy systems.
DVWA Low — file upload and shell execution walkthrough
move_uploaded_file($uploaded_tmp, $target_path); // no check whatsoever
Validation Types — Why Common Defences Fail
Most developers understand they need to validate uploaded files. The problem is that the most obvious validation approaches are all bypassable. Understanding why each approach fails is what makes a tester effective — and what makes a developer build something that actually works.
Validation Method
What It Checks
Why It Fails
Bypass
File extension check
Last characters of the filename
Attacker controls the filename completely
shell.php5, shell.phtml, ShElL.pHp
Content-Type header
MIME type in HTTP request
Header is sent by the client — fully attacker-controlled
Set Content-Type: image/jpeg in Burp
Client-side JS validation
File type in browser before upload
Intercepted and bypassed with Burp — JS doesn’t run on server
Intercept in Burp, change filename
Magic bytes check
First bytes of file content
Better — but PHP executes even if valid JPEG header precedes PHP code
Prepend valid image bytes before PHP
Content validation library
Full image re-processing
Most robust — re-encoding destroys embedded code
Very hard to bypass if properly implemented
Bypass Techniques — Extension & MIME Manipulation
Extension bypass techniques — when .php is blocked
# PHP alternative extensions — parsed by Apache/PHP as PHP
shell.php3 shell.php4 shell.php5 shell.php7
shell.phtml shell.pht shell.phps shell.phar
# These are valid PHP file extensions — blocked by extension
# filters only if those specific extensions are in the blocklist
# Case variation — bypasses case-sensitive extension checks
shell.PHP shell.Php shell.pHp shell.PHp
# Double extension — some servers execute based on last extension
shell.jpg.php shell.png.php
# Some servers execute: shell.php.jpg (if Apache has AddHandler)
shell.php%00.jpg ← server stores as shell.php, check passes on .jpg
# Fixed in PHP 5.3.4 — but still works on very old installations
# .htaccess upload — changes how a directory is interpreted
# Create file named “.htaccess” with contents:
AddType application/x-httpd-php .jpg
# If .htaccess can be uploaded, all .jpg files in that dir execute as PHP
# Then upload shell.jpg — it executes as PHP
LEVEL 2
DVWA Medium — MIME Type Bypass via Burp Suite
DVWA’s Medium security level adds a MIME type check — it verifies that the uploaded file’s Content-Type header claims to be an image. This sounds more secure. But because the Content-Type header is sent by the browser (and controllable by the attacker through Burp), it can be trivially modified mid-upload.
Some applications go further and check the actual file content — specifically the magic bytes (file signature) at the start of the file. A JPEG always starts with FF D8 FF. A PNG starts with 89 50 4E 47. This approach is stronger than MIME type checking but still has a weakness: PHP executes its code block even if valid image bytes precede it.
Magic bytes bypass — embedding PHP in a valid image file
# Method 1: Prepend GIF magic bytes manually
# Create a file starting with “GIF89a” (valid GIF header)
Even after a successful upload, the attacker needs to know where the file was stored and whether it’s accessible. Finding the upload path is sometimes the hardest part of a real-world file upload exploitation attempt.
Finding the uploaded file — methods used in real assessments
# 1. Application reveals it — check success message
“File uploaded to: /uploads/1712345678_shell.php”
# Some apps helpfully tell you exactly where the file went
# 2. Predictable path based on original filename
/uploads/shell.php → try common upload directories
# 6. Burp HTTP History — check image src attributes in responses
# After upload, the app may render the “image” somewhere on the page
# The src attribute of that img tag reveals the upload path
Secure File Upload — Implementation That Actually Holds
The lesson from the bypass techniques above is that any single validation control can be bypassed. Secure file upload requires multiple independent layers — each one addressing a different bypass vector. Remove any single layer and an attacker with sufficient creativity will find the gap.
✓
Validate file content using an image processing library
Use PHP’s getimagesize() or the Intervention Image library to verify the file is a genuine image. Better yet, re-encode it — run the uploaded image through imagejpeg(imagecreatefromstring($data)). Re-encoding destroys any embedded PHP code, making the bypass techniques above ineffective.
✓
Rename files to a random server-generated name
Never preserve the original filename. Generate a UUID or random hash as the stored filename with a safe extension: uuid4() + '.jpg'. This prevents double-extension attacks and eliminates the attacker’s ability to predict the stored filename.
✓
Store files outside the web root or use cloud storage
The most effective control: store files where they cannot be accessed via URL. Upload to /var/uploads/ (not /var/www/html/uploads/) and serve through a download handler that reads and streams the file. Or use S3/GCS with pre-signed URLs. Even if a PHP file is uploaded, it cannot be executed because it’s not in the web root.
✓
Disable script execution in upload directories
If files must be in the web root, use server configuration to prevent script execution. In Apache, add to the upload directory’s .htaccess: php_flag engine off and Options -ExecCGI. In Nginx: configure the upload location block to not pass *.php requests to PHP-FPM.
✓
Enforce an allowlist (not blocklist) of accepted extensions
Block lists of dangerous extensions are always incomplete — there are too many PHP variants. Instead, maintain a strict allowlist of exactly what is permitted: ['.jpg', '.jpeg', '.png', '.gif', '.webp']. Everything else is rejected regardless of claimed type.
Confirm command execution. Run at least 4 different commands and document the output. What user is the web server running as?
2
Bypass DVWA Medium using Burp Suite MIME spoofing
Set DVWA to Medium. Intercept the upload request in Burp. Change the Content-Type header from application/octet-stream to image/jpeg while keeping the file as shell.php. Forward and confirm the upload succeeded. Execute a command to prove it worked.
3
Create a GIF89a polyglot and test the magic bytes bypass
Try uploading shell2.php on Medium with both original Content-Type and the spoofed image/gif type. Does the GIF header help bypass the check?
★
Clean up — delete your shells after testing
# Use the shell to delete itself (lab cleanup) curl “http://192.168.56.101/dvwa/hackable/uploads/shell.php?cmd=rm+/var/www/dvwa/hackable/uploads/shell*.php”
Professional habit: always clean up web shells after demonstrations — both in labs and real assessments. Leaving web shells on client systems creates ongoing security risk.
⭐ BONUS CHALLENGE — DVWA High Security Level
Set DVWA to High security. The file upload now combines extension checking, MIME type checking, and file size limits. Examine the DVWA High source code (click “View Source” in DVWA). What checks are present? Can any of the bypass techniques from this lesson get through? What additional technique — combining the File Inclusion vulnerability from Day 16 — might complete the exploit chain? Research “DVWA High file upload bypass” to understand the intended solution. Share your method with #Day18Done 🐚
🐚
File upload vulnerabilities demystified. You now understand every step — and every defence.
Day 19 covers Command Injection — a vulnerability category directly related to what we did today. Where file upload achieves RCE by uploading and executing code, command injection achieves it by injecting OS commands directly into application functionality. Same destination, different path — and equally dangerous when undefended.
Can file upload vulnerabilities affect non-PHP applications?
Absolutely. Every server-side language has equivalent risks: ASP/ASPX files on IIS-hosted applications, JSP files on Java servers (Tomcat, JBoss), Python scripts on Django/Flask, Perl scripts, and even Node.js. The specific file extension and syntax differs, but the vulnerability category is identical — upload an executable, access it via browser, achieve RCE. Security researchers also look at SVG files (can embed JavaScript, leading to XSS), XML files (XXE vulnerability), and PDF files (can trigger various parsing vulnerabilities).
Does storing files on AWS S3 prevent file upload vulnerabilities?
Using cloud storage like S3 eliminates the server-side execution risk — S3 serves files as static objects, not as PHP scripts. This is one of the most effective mitigations. However, new risks emerge: public S3 buckets expose all uploaded files, malicious SVG files served from S3 can execute JavaScript in the viewer’s browser, and zip files with path traversal payloads (“zip slip”) can be dangerous if the application extracts them. Cloud storage is safer for RCE prevention, but proper content validation remains important.
How do I find file upload endpoints during a bug bounty assessment?
Common locations include: profile picture/avatar uploads, document submission forms, support ticket attachments, content management import features, resume or CV upload portals, and API endpoints that accept multipart/form-data. Use Burp’s HTTP History to find all requests with that content type during normal application browsing. Also examine JavaScript source for API endpoints that might accept file uploads but aren’t linked from the visible UI. Internal tools, admin panels, and recently added features are particularly valuable targets.
What should I do with a web shell after a penetration test?
Web shells must be removed immediately after demonstrating the vulnerability — they represent a persistent backdoor that any other party could discover and use. Professional practice: document the full path and filename before cleanup, screenshot the successful execution as evidence, then delete the file and confirm deletion. Include the full path in your report so the client can independently verify removal. Some engagement contracts require the client to confirm deletion in writing. Never leave web shells running between testing sessions, even in lab environments.
File upload is where I’ve seen junior testers get confused — they find the vulnerability but can’t execute it because they can’t find the upload path, or the shell returns a blank response because execution is disabled. The methodical approach matters here: verify upload success, locate the file, verify execution, then run commands. Each step has a check. Work through them in order and the exploit chain becomes reliable.
Founder of Securityelites and creator of the SE-ARTCP credential. Working penetration tester focused on AI red team, prompt injection research, and LLM security education.