File uploads: the door we keep leaving open
Why “upload a file” is still one of the easiest ways into a web application, and a short checklist that actually holds.
Almost every product lets people upload something. A profile photo. An invoice. A CV. A scanned ID. It feels like the most ordinary feature in the world, so it rarely gets the attention it deserves.
From an attacker’s point of view, though, an upload form is a polite invitation: please put my file on your server. Everything that happens next (where the file is stored, what reads it, who can open it later) decides whether that invitation was harmless or the start of a very bad week.
What usually goes wrong
1. Trusting what the browser says
The file name, the extension and the Content-Type header all come from the user. All three can say anything. A file called avatar.jpg with an image/jpeg header can still be a script. Checking only these values is like checking a visitor’s badge that they printed themselves.
2. Storing uploads where the server will run them
The classic disaster: uploads land in a folder inside the web root, the web server is configured to execute scripts there, and someone uploads a small “web shell”. One request later, they are running commands on your server. It is old, it is well known, and it still happens.
3. Keeping the original file name
User-supplied names bring their own problems: names that try to climb out of the upload folder (../../), names that overwrite someone else’s file, names that break your logs or your HTML. There is almost never a good reason to keep them on disk.
4. No limits
No maximum size, no limit per user, no limit on how many files an archive expands into. That is how one upload fills a disk, or how a tiny “zip bomb” turns into gigabytes the moment your code unpacks it.
5. Serving files back from your main domain
Files that users can open again, such as SVG images, HTML, or anything the browser might “sniff” as HTML, can carry JavaScript. Served from your main domain, that script runs with your users’ sessions. That is stored cross-site scripting, delivered through a feature everyone thought was just “downloads”.
6. The parser is the weak point
Sometimes the file is fine and the library reading it is not. Image converters, PDF tools and document parsers have all had serious bugs. The ImageTragick flaw in ImageMagick (2016) let a crafted image run commands on servers that only wanted to make thumbnails. Office files and SVGs are XML underneath, which brings XML attacks along with them.
A checklist that actually holds
- Allow, don’t block. Decide the few file types you really need, and accept only those. Check the real content (the file’s first bytes), not just the extension.
- Rename everything. Store files under a random name or ID you generate. Keep the original name in the database if you need to show it.
- Store outside the web root. Better still, use separate object storage. The place where files live should never be able to execute anything.
- Serve carefully. Use a separate domain for user files where you can. Send
Content-Disposition: attachmentfor anything that isn’t meant to display in the page, plusX-Content-Type-Options: nosniff. - Set limits. Maximum size, maximum files per user, and a ceiling on what archives may expand into.
- Re-process images. Re-encode uploaded images into a fresh file. It strips hidden content and metadata along the way.
- Isolate the parsers. Run conversions and thumbnails in a separate, low-privilege worker, keep those libraries updated, and scan documents before anyone opens them.
- Log and watch. Record who uploaded what and when. You will want that history the day something looks wrong.
For the full technical detail, the OWASP File Upload Cheat Sheet is the best reference and worth bookmarking.
The boring version is the safe version
None of this is clever. That is the point. Upload features get broken through the simple gaps, not the exotic ones, and closing those gaps costs very little when you do it early. If your product accepts files, test that feature the way an attacker would: upload the wrong type, the huge file, the strange name, and see what your system does with it.