- Zero-Day Wire
- Posts
- Defend the Web (Week 6): File Upload Defense
Defend the Web (Week 6): File Upload Defense
How to Secure File Uploads, Block Web Shells & Prevent Remote Code Execution Attacks
π‘οΈ Week 6: File Upload Defense
Defend the Web: Part 6 of 8
Welcome back to Defensive Wednesday.
Hey π
Yesterday you weaponized file uploads. You disguised web shells as profile pictures, bypassed filters with null bytes and polyglot files, and took over servers. You turned "upload your resume" into remote code execution. If you missed it, catch up here π Week 6
Today? We lock it down.
You're the developer now. Your job is to make sure every file that lands on your server is safe β even when attackers throw shell.php.jpg and magic byte tricks at every upload form.
Here's what most devs get wrong β they think checking file extensions is enough. It's not even close.
File uploads are the front door to your server. The Equifax breach started here. Ransomware campaigns use it as an entry point. Bug bounty hunters find these weekly and collect $10K+ payouts.
The fix? Actually hard. Way harder than "just check the extension."
You need to validate everything. Store files outside the web root. Rename them. Scan them. Treat every upload as hostile.
Let's build upload systems that don't get pwned.
π°What if Business news was actually... fun to read?
Click here π
Business news worth its weight in gold
You know whatβs rarer than gold? Business news thatβs actually enjoyable.
Thatβs what Morning Brew delivers every day β stories as valuable as your time. Each edition breaks down the most relevant business, finance, and world headlines into sharp, engaging insights youβll actually understand β and feel confident talking about.
Itβs quick. Itβs witty. And unlike most news, itβll never bore you to tears. Start your mornings smarter and join over 4 million people reading Morning Brew for free.
π― The Core Problem
Every file upload RCE happens because your server executes user-uploaded files as code.
When users upload files and you serve those files directly from a web-accessible directory, you're asking for trouble.
They upload shell.php. Your server stores it in /uploads/. They visit yoursite.com/uploads/shell.php. Your server executes it. Game over.
Three golden rules:
Never store uploads in the web root β Files should never be directly accessible via URL
Validate everything β Extension, MIME type, content, size, magic bytes
Rename and relocate β Change filename, strip path info, store outside web directory
Follow these, and file upload RCE becomes nearly impossible.
π Defense #1: Store Uploads Outside Web Root
This is your number one defense. Not optional.
The problem: If uploads are in /var/www/html/uploads/, attackers access them at yoursite.com/uploads/shell.php.
The solution: Store files completely outside your web directory.
Where to store files:
β
/var/uploads/ (outside /var/www/)
β
/opt/app-uploads/
β
Cloud storage (S3, Azure Blob, Google Cloud Storage)
β
Separate file server
How to serve them safely:
Use a download script that validates permissions and serves files programmatically.
Bad: User visits yoursite.com/uploads/file.php β Server executes
Good: User requests yoursite.com/download?id=abc123 β Script validates β Reads from /var/uploads/abc123.bin β Serves with proper headers
Your download script checks permissions, file type, and forces download instead of execution.
Resources:
π OWASP File Upload Cheat Sheet
π Secure File Upload Architecture
π AWS S3 Secure Upload Guide
Why this matters: Even if attackers upload a perfect web shell, the file lives outside the web root. The server never executes it.
π Defense #2: Whitelist File Extensions
Extension validation is your first line. But most devs do it wrong.
Bad (blacklist): Block .php, .exe, .sh
Attackers bypass with .php5, .phtml, .phar, .PhP.
Good (whitelist): Allow ONLY .jpg, .jpeg, .png, .gif, .pdf. Everything else gets rejected.
Implementation:
β
Strict whitelist of allowed extensions
β
Check extension after the last dot
β
Convert to lowercase before checking
β
Strip null bytes from filename
β
Remove path traversal attempts (../, ..)
β
Block double extensions
Resources:
π File Extension Validation
π Multer (Node.js)
π Django File Upload Security
Why this matters: Whitelisting blocks every bypass. Null bytes? Stripped. Double extensions? Rejected. Case variations? Normalized.
π Defense #3: Validate MIME Types & Magic Bytes
MIME type validation catches some attacks, but attackers can spoof headers. Use server-side detection.
Server-side MIME detection:
Use libraries that read actual file content, not just the Content-Type header.
Expected types:
Images: image/jpeg, image/png, image/gif
PDFs: application/pdf
Magic bytes (file signatures):
JPEG: FF D8 FF
PNG: 89 50 4E 47
GIF: 47 49 46 38
PDF: 25 50 44 46
Read the first bytes of uploaded files. Compare against expected signatures. Reject mismatches.
Resources:
π python-magic
π file-type (Node.js)
π PHP fileinfo
π File Signature Database
Why this matters: Attackers send shell.php with Content-Type: image/jpeg. Server-side magic number detection catches it.
π Defense #4: Rename Files & Strip Metadata
Never trust user-provided filenames.
Renaming strategy:
β
Generate random filename (UUID, hash, timestamp)
β
Strip all path information
β
Store mapping in database (original β safe name)
β
Never use user input in file paths
Example:
Original: ../../etc/passwd.jpg
Stored as: a3f5e892-4c3b-11ed-bdc3-0242ac120002.jpg
For images, re-encode them to strip embedded code and metadata. This destroys polyglot files.
Image processing libraries:
π Sharp (Node.js)
π Imagick (PHP)
π exiftool β Metadata stripping
Why this matters: Path traversal attempts become meaningless. Polyglot files don't survive re-encoding.
π Defense #5: Disable Script Execution in Upload Directories
Even if a shell lands in your uploads folder, prevent execution.
Apache (.htaccess in upload directory):
<FilesMatch "\.ph(p[3457]?|t|tml)$">
Deny from all
</FilesMatch>
nginx (in server block):
location /uploads/ {
location ~ \.php$ {
deny all;
}
}
Resources:
π Apache Security Tips
π nginx Security Controls
Why this matters: Defense in depth. Even if shell.php gets through, the web server refuses to execute it.
π Defense #6: Scan with Antivirus & Set File Limits
Run every uploaded file through scanning before storing.
Scanning workflow:
Upload β Temporary quarantine β Run scan β If clean: move to storage β If infected: delete and alert
Scanning solutions:
π VirusTotal API β Multi-engine scanning
π YARA Rules β Malware pattern matching
File size limits:
Set maximum sizes to prevent DoS attacks.
Recommended limits:
Profile pictures: 2-5 MB
Documents: 10-20 MB
General uploads: 5 MB default
Configure at web server level (nginx client_max_body_size, Apache LimitRequestBody) and application level.
Resources:
π nginx Upload Limits
π Apache Upload Limits
Why this matters: Known malware gets caught. Automated scanners can't flood your storage with massive files.
π Defense #7: Use Secure Headers & Rate Limiting
When serving files, force downloads and prevent execution.
Headers to set:
Content-Disposition: attachment; filename="user-file.pdf"
Content-Type: application/octet-stream
X-Content-Type-Options: nosniff
Content-Disposition: attachment forces download, prevents rendering.X-Content-Type-Options: nosniff prevents MIME-sniffing attacks.
Rate limiting:
β
Limit uploads per user per time period
β
Track upload attempts by IP
β
Implement CAPTCHA for suspicious activity
Example limits:
10 uploads per hour per user
3 failed uploads trigger CAPTCHA
20 uploads per day for new accounts
Resources:
π Content-Disposition Header
π Express Rate Limit
π Django Ratelimit
Why this matters: XSS in uploaded HTML fails. Automated attacks can't brute-force bypasses.
π Defense #8: Cloud Storage & Logging
If using cloud storage, configure it securely.
Cloud storage security:
β
Never allow public write access
β
Use presigned URLs for uploads
β
Set bucket policies correctly
β
Enable versioning and lifecycle policies
β
Use separate buckets for uploads vs. public content
Resources:
π AWS S3 Security Best Practices
π Azure Blob Security
π Google Cloud Storage Security
Comprehensive logging:
Log every upload attempt, validation failure, scanner result, and file access. Monitor for unusual patterns.
What to monitor:
π¨ Unusual file extensions
π¨ Large numbers of failed uploads
π¨ Repeated validation failures
π¨ Scanner detections
Resources:
π OWASP Logging Cheat Sheet
π ELK Stack
Why this matters: Misconfigured S3 buckets are a top vulnerability. Logging helps catch attacks in progress.
π οΈ Essential Defense Tools
Validation:
π python-magic β MIME detection
π file-type β Node.js type detection
π Apache Tika β Java content detection
Scanning:
π YARA β Pattern matching
π VirusTotal β Multi-engine scanner
Image Processing:
π Sharp β Node.js images
Testing:
π Burp Suite β Upload testing
π Upload Scanner β Burp extension
π Fuxploider β Automated scanner
π Learning Resources
π OWASP File Upload Cheat Sheet β Complete reference
π PortSwigger File Upload Guide β Attack and defense
π CWE-434: Unrestricted Upload β Official weakness description
That's Week 6 defense done. π‘οΈ
Next Tuesday: SSRF & Internal Access β we're making servers attack themselves and accessing internal networks.
Next Wednesday: SSRF Defense β how to prevent server-side request forgery and protect internal resources.
See you then.
β Zwire βοΈ
Your Feedback MattersDid You Enjoy This Weekβs Defensive Tutorial? |
P.S. Got questions about implementing these defenses? Reply to this email. I read everything.


Reply