InfoSec Notes
Securing file upload features against malicious uploads including shell injection, path traversal, MIME type attacks, and implementing proper server-side validation.
Overview
File upload functionality is one of the most dangerous features in web applications. Improper handling can lead to remote code execution, server compromise, cross-site scripting, and denial of service.
Common File Upload Attacks
| - Access uploaded file URL | Remote Code Execution |
| - Filename | "../../../etc/cron.d/backdoor" |
| - Upload .html disguised as image | Stored XSS |
Secure File Upload Implementation
import os
import uuid
import hashlib
import magic # python-magic for content detection
from pathlib import Path
from werkzeug.utils import secure_filename
class SecureFileUploader:
"""Production-secure file upload handler."""
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.docx'}
ALLOWED_MIMETYPES = {
'image/jpeg', 'image/png', 'image/gif',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
UPLOAD_DIR = '/var/uploads/user_files' # Outside web root!
def validate_and_store(self, file_stream, original_filename: str,
user_id: int) -> dict:
"""Securely validate and store uploaded file."""
# 1. Check file size
file_stream.seek(0, 2) # Seek to end
size = file_stream.tell()
file_stream.seek(0) # Reset to beginning
if size > self.MAX_FILE_SIZE:
return {"error": "File too large", "max_size": self.MAX_FILE_SIZE}
if size == 0:
return {"error": "Empty file"}
# 2. Validate extension (whitelist approach)
ext = Path(original_filename).suffix.lower()
if ext not in self.ALLOWED_EXTENSIONS:
return {"error": f"Extension {ext} not allowed"}
# 3. Validate MIME type (content-based, not header-based)
content = file_stream.read(2048)
file_stream.seek(0)
detected_mime = magic.from_buffer(content, mime=True)
if detected_mime not in self.ALLOWED_MIMETYPES:
return {"error": f"Content type {detected_mime} not allowed"}
# 4. Generate safe storage name (never use user-provided name)
safe_name = f"{uuid.uuid4().hex}{ext}"
# 5. Store OUTSIDE web root
storage_path = os.path.join(self.UPLOAD_DIR, str(user_id), safe_name)
os.makedirs(os.path.dirname(storage_path), exist_ok=True)
# 6. Write file
with open(storage_path, 'wb') as f:
while chunk := file_stream.read(8192):
f.write(chunk)
# 7. Calculate hash for integrity
file_hash = hashlib.sha256(open(storage_path, 'rb').read()).hexdigest()
return {
"success": True,
"stored_name": safe_name,
"original_name": secure_filename(original_filename),
"size": size,
"mime_type": detected_mime,
"hash": file_hash
}Prevention Checklist
| Control | Implementation |
|---|---|
| Extension whitelist | Only allow specific safe extensions |
| Content-type validation | Use magic bytes, not Content-Type header |
| Rename files | UUID-based names, never user-provided |
| Store outside webroot | /var/uploads/ not /var/www/uploads/ |
| Set noexec | Mount upload directory with noexec flag |
| Size limits | Both per-file and total quota |
| Scan for malware | ClamAV or cloud scanning service |
| Strip metadata | Remove EXIF data from images |
| Serve via CDN | Different domain for user content |
Interview Questions
- Why should uploaded files be stored outside the web root?
- If stored within the web root, an attacker could upload a PHP/JSP file and access it directly via URL, causing the server to execute it. Storing outside web root means the file cannot be accessed via URL — it must be served through application code that controls access and sets correct headers.
- Why is checking the file extension not sufficient for security?
- Attackers can use double extensions (shell.php.jpg), null bytes (shell.php%00.jpg), MIME type mismatches, or polyglot files. Extension checking is one layer but must be combined with content-type detection (magic bytes), serving with correct Content-Type and Content-Disposition headers.
- How would you securely serve user-uploaded files?
- Serve from a separate domain (prevents cookie access), set Content-Disposition: attachment (forces download), set X-Content-Type-Options: nosniff (prevents MIME sniffing), validate content type on serve, use CDN with limited permissions, and set restrictive CSP on the serving domain.
- What is a polyglot file and why is it dangerous?
- A polyglot file is valid in multiple formats simultaneously (e.g., valid JPEG that's also valid HTML/JavaScript). It can pass image validation but when served with wrong Content-Type or in certain contexts, the browser interprets it as executable content, enabling XSS.
- How do you prevent ZIP bomb attacks through file uploads?
- Set maximum decompressed size limits, check compression ratio (suspicious if > 100:1), use streaming decompression with size counting, set resource limits (CPU time, memory), and validate recursion depth for nested archives.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for File Upload Security.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Information Security topic.
Search Terms
information-security, information security, information, security, web, file, upload, file upload security
Related Information Security Topics