Lab 02 — Secure Upload Planner
Difficulty: 3/5 | Runs locally: yes
Pairs with the Phase 02 WARMUP Chapter 11 (path traversal / unsafe upload).
Why This Lab Exists (Purpose & Goal)
File upload is one of the most dangerous features a web application can offer, because it hands an attacker control over both a path and the content lifecycle on your servers. Done carelessly it yields path traversal, stored XSS, server-side request forgery via processing, remote code execution through image/document parsers, and storage exhaustion. The goal of this lab is to design upload storage planning that is safe by construction — one that never trusts the filename or the claimed content type, generates its own object names, and isolates untrusted bytes from the moment they arrive.
The Concept, In the Weeds
The root cause of upload bugs is trusting attacker-supplied metadata. The defenses each remove a specific trust:
- Never trust the filename.
../../etc/cron.d/evil, a name with embedded NUL, or a double extension (invoice.pdf.exe) all weaponize the path. The fix: generate the server-side object name yourself and store the original only as inert metadata. - Never trust the MIME claim.
Content-Type: image/pngis whatever the client says; the bytes may be a polyglot or a script. Validate against an allowlist of media types you actually support, and treat the content as untrusted regardless. - Bound everything before you store or process. Exact byte limits stop storage abuse; isolating processing (thumbnailing, virus scanning, parsing) in a sandbox (Phase 06) stops a malicious file from exploiting the processor.
- Quarantine first. Untrusted uploads land in an isolated location, are validated, and only then are released — never served back from a path the attacker influenced.
The deeper idea connects to Phase 01: an upload is just untrusted input crossing a trust boundary, and the upload's later consumer (the image library, the browser that renders it, the malware scanner) is a parser that can be attacked.
Why This Matters for Protecting the Company
A single insecure upload endpoint has, in real incidents, led to full server compromise (a web shell written to a web-served path) and to mass stored-XSS against every user who views the file. When you can plan upload storage that generates names, isolates content, enforces exact limits, and processes in a sandbox, you remove an entire category of high-severity findings from any product that lets users upload anything — profile photos, attachments, documents, data imports.
Build It
Implement the upload planner: enforce byte limits, an allowed-media-type allowlist, generated object names, quarantine, isolated processing, and traversal-safe metadata. The lab does not execute uploaded content — it plans where and how it would be safely stored.
LAB_MODULE=solution pytest -q
Secure Implementation Patterns
The anti-pattern. Trusting the client's filename and MIME type, serving from a derived path:
path = os.path.join(UPLOAD_DIR, request.filename) # ../../var/www/shell.php -> traversal/RCE
open(path, "wb").write(request.body) # MIME trusted; no size cap; web-served path
The secure pattern — generate the name, never trust client metadata (mirrors solution.py):
ALLOWED = {"image/png", "image/jpeg", "application/pdf"}
HEX64 = re.compile(r"^[0-9a-f]{64}$")
def plan_upload(filename, content_type, size, digest) -> dict:
if size <= 0 or size > 10 * 1024 * 1024: raise ValueError("invalid size") # exact cap
if content_type not in ALLOWED: raise ValueError("unsupported type") # allowlist
if not HEX64.fullmatch(digest): raise ValueError("invalid digest") # strict shape
if "\x00" in filename or "/" in filename or "\\" in filename or filename in {".", ".."}:
raise ValueError("unsafe filename") # reject path tricks
return {
"object_name": f"quarantine/{digest}", # SERVER-GENERATED name, in quarantine, not web-served
"original_name": filename[:255], # kept only as inert metadata
"declared_type": content_type, # a CLAIM to re-verify by content sniffing later
"executable": False, "public": False, "scan_required": True,
}
The object name is the content digest in a quarantine prefix — the attacker controls neither the path nor whether it's web-served. The original filename survives only as a display string.
Production practices to carry forward:
- Re-verify content, don't trust
Content-Type— sniff magic bytes / transcode the image; the declared type is a hint, the bytes are the truth. - Process untrusted files in a sandbox (Phase 06): thumbnailing, virus scanning, and parsing all attack the processor (ImageTragick, font/codec CVEs). Default-deny network, drop privileges.
- Serve via signed, expiring URLs from an isolated bucket — never from a path the user influenced,
never with an
executable/render-as-HTMLcontent type for user content. - Quarantine → validate → release: nothing is reachable until scanning passes.
- Bound everything: size, count, and concurrency, to stop storage/DoS abuse.
Validation — What You Should Be Able to Do Now
- Explain why the filename and the MIME type are both attacker-controlled and must be regenerated / re-validated, never trusted.
- Describe the quarantine → validate → isolated-process → release pipeline and what each stage protects.
- Connect an upload to its downstream consumer (the image parser, the browser) and reason about attacking the consumer.
The Broader Perspective
This lab reinforces a principle you will apply everywhere untrusted data enters a system: strip the attacker's control over names, paths, types, and the processing environment, and treat the content as hostile until proven otherwise. The same "generate your own identifiers, isolate the processing, bound the resources" instinct protects email attachments, CI artifact handling, document pipelines, and — at the extreme — the sandboxed execution of untrusted code in Phase 06. Upload security is just input security with a filesystem and a content processor attached.
Interview Angle
- "How do you secure a file-upload feature?" — Generate server-side names (never trust the filename); allowlist media types and treat content as untrusted; enforce exact size limits; quarantine and process in isolation; serve from a path the user can't influence; sandbox any parsing/transcoding.
Extension (Stretch)
Add an isolated scanner/transformer step and a quarantine-release workflow that only promotes a file after validation succeeds.
References
- Phase 02 WARMUP Chapter 11; OWASP File Upload Cheat Sheet.
- Phase 06 (sandboxed processing of untrusted content).