File Upload Vulnerabilities: The Complete Practical Guide
How to Prevent File Upload Vulnerabilities
Securing file uploads: allow-list validation, image re-encoding, random names, out-of-web-root storage, disabling execution, and entity scanning.
Published
Every technique in this cluster exists because an upload trusted something it should not have: the filename, the content type, the first bytes, or the storage location. Preventing upload flaws is about removing that trust in layers, because each individual control has a known bypass. Done together, they turn an upload from a reliable route to a web shell into an inert file nobody can misuse.
Allow-list and validate server-side
Decide what you accept and reject everything else, on the server:
- Use an allow-list of permitted types, never a blacklist of banned ones. A blacklist always misses an extension.
- Validate server-side. The client, the
Content-Typeheader, and the filename are all attacker-controlled and prove nothing. - Check the real content, not just the name, but do not rely on a content sniff alone, since magic bytes are forgeable.
The allow-list is the floor, not the whole building.
Re-encode images and strip metadata
For images, decode the upload and re-save it as a fresh file. Re-encoding rebuilds the image from its pixel data and discards everything else, appended code, metadata, and polyglot tricks, so embedded payloads die. Strip metadata in the process. This is the control that defeats magic-byte prefixes and most polyglots, and it should be standard for any image upload.
Rename to a random server-generated name
Discard the user's filename entirely and generate your own: a random, unpredictable name with one fixed, safe extension. This kills three attacks at once, path traversal in the filename, extension tricks, and predictable storage paths that help race conditions. The user never needs their original filename, so there is no reason to keep it.
Store outside the web root and serve through a handler
Do not store uploads anywhere the server can execute them. Put them outside the web root, and serve them back through application code that:
- Sets a safe
Content-Type, so an SVG is not rendered as markup. - Sets
Content-Disposition: attachment, so files download instead of rendering inline, which neutralizes stored XSS from SVG and HTML. - Enforces access control on who can retrieve the file.
A file that can never be executed and is always served as an attachment cannot become a web shell or a stored-XSS payload.
Disable execution, cap size, and scan
Round out the layers:
- Disable script execution in the upload directory at the server level, so even a file that slips through cannot run.
- Enforce size limits to blunt decompression bombs and resource exhaustion.
- Scan contents where appropriate, and for XML, Office, and SVG files, disable external entity resolution on the parser to stop XXE and SSRF.
These catch what the earlier layers miss and cut off the non-RCE attacks.
Why it has to be layered
No single control is enough, because each maps to a bypass in this cluster: extension checks fall to alternate extensions, content sniffing falls to magic bytes and polyglots, and even re-encoding leaves SVG and document parsing open. Layered together, deny-by-default allow-listing, re-encoding, random renaming, out-of-web-root storage, disabled execution, and entity-safe parsing, they remove every foothold the attacks rely on. To understand what you are defending against, go break it on a free live target.
← Back to File Upload Vulnerabilities: The Complete Practical Guide