Treat every upload as an attack until you have checked it. Use these controls:
- Limit size and count. Set a size limit in the upload parser, so it rejects large files early. Also limit how many files one request can send.
- Check the type by content. Compare the type against an allowlist (a list of the only types you accept). Read the file's first bytes, called magic bytes, which show the real format. Don't trust only the extension or the
Content-Typeheader the client sends, because the attacker controls both. - Make a new random filename. Never use the user's filename in the path. This prevents path traversal, where a name like
../../etc/cron.d/xwrites outside the upload folder. It also stops one upload from overwriting another file. - Store files in a safe place. Keep them outside the web root (the folder the web server serves directly), or in object storage such as S3.
- Serve files with safe headers. Send the correct
Content-Type, plusX-Content-Type-Options: nosniffand, where possible,Content-Disposition: attachment. - Use a separate domain if you can. Serve uploads from another domain, so an uploaded HTML or SVG file can't run script on your own origin.
- Clean and scan. Re-encode images, which removes hidden payloads and metadata. Scan files for malware. Keep image and file processing libraries patched.
- Set limits on file size and on the number of files.
- Check the file type against an allowlist using its content, not its extension or header.
- Make random filenames on the server, so path traversal can't happen.
- Store files outside the web root and serve them with safe headers or from another domain.
- Re-encode images, scan for malware and keep processing libraries patched.
Read more