Audit BenchAi
← All posts

Reviewing File Upload Features for Security Risk

·7 min read

File upload features are everywhere — avatars, attachments, document imports, bulk data loads — and they are consistently under-reviewed for a simple reason: the happy path is easy to verify by testing. Upload a normal image, confirm it displays, ship it. The failure modes only show up when someone deliberately uploads a file crafted to be something other than what it claims to be, which is exactly the scenario a quick manual test never covers.

Client-side validation is a UX nicety, not a control

An accept="image/*"attribute or a JavaScript extension check makes the browser's file picker more convenient — it does nothing to stop anyone who sends the upload request directly, bypassing the browser UI entirely. Every validation rule that matters for security has to be enforced server-side, on the bytes that actually arrive, regardless of what the client claimed about them.

Checking the extension is not checking the file

A file named invoice.jpg can contain anything — including a working PHP or JSP script, renamed specifically to defeat an extension check. Validating actual file content, not the filename, is what matters: checking magic bytes against the claimed type, or using a real content-sniffing library rather than trusting the Content-Typeheader the client sent. Even content-based checks aren't bulletproof — polyglot files can be crafted to be simultaneously valid as two different formats, which is one more reason defense in depth matters here more than a single clever check.

Where an uploaded file gets executed unintentionally

  • Storing uploads inside a directory the web server will execute scripts from, so a file that slips past validation as .php or .jsp runs as code the moment it's requested.
  • Serving uploaded SVG or HTML files inline with your application's own origin — both formats can carry embedded <script> content, turning a file upload feature into a stored XSS vector.
  • Image-processing libraries used to resize or transform uploads, which have their own history of exploitable parsing bugs when fed a deliberately malformed file.

Practical controls worth checking for

  1. Uploaded files are stored outside the web root, or in object storage with no execute permission at all.
  2. Uploads are served from a separate domain or subdomain, isolating any script they might contain from your main application's session cookies.
  3. Images are re-encoded rather than having the original bytes passed straight through, which strips most embedded exploits along the way.
  4. A real file size limit is enforced server-side, not just suggested in the UI.
  5. Malware scanning is applied where the risk profile justifies it — a public-facing upload feature handling arbitrary file types is a very different risk than an internal tool with a handful of trusted users.

Test it like an attacker would, not like a user would

The most useful review question for an upload feature isn't "does a normal file work" — it's "what happens if I upload a script renamed to look like an image, or an SVG with a payload inside, or a file ten times larger than expected." If nobody on the team can answer that with confidence, the feature isn't actually ready, regardless of how clean the happy path looks.

Read the secure coding checklist →

See how to review rate limiting and abuse prevention →