Secure file upload in ASP.NET Core: the checks that matter
A form that accepts files is the most dangerous form on a website. If someone can upload a script and then open it, they can run code on your server. Most of the risk comes from a handful of well-known tricks. Here they are in plain English, with what ASP.NET Core's RichTextBox endpoint does about each. We test these refusals against our own live site.
"I'll upload a script"
The trick: upload shell.aspx, then visit it.
The check: an allow-list. Only the extensions you list are accepted, so .aspx, .php and .exe never get in. Asked to take a .php file, the endpoint refuses it with "invalid file extension" and stores nothing.
"I'll disguise it as an image"
The trick: name the script photo.jpg, and send image/jpeg as its type. The sender writes both, so neither proves anything.
The check: the file's first bytes must match its extension (ValidateUploadMagicBytes, on by default). A script named .jpg gets "file content does not match extension".
"I'll hide a script in an SVG"
The trick: an SVG is a picture that can contain JavaScript. Served from your own site, that script runs with your visitors' cookies.
The check: SVG is not on the default list at all. If you add it, a validator still refuses an SVG that contains a script, an event handler, a javascript: link, a foreignObject, an embedded document or an XML entity.
"I'll send a huge picture"
The trick: a few kilobytes can declare a picture of 30000 by 30000 pixels, which every reader's browser then tries to decode.
The check: MaxImagePixels reads the size from the header, before anything decodes the image, and refuses it.
"I'll fill your disk"
The check: MaxUploadBytes limits each file. There is no total quota in the package, so limit the rate with RequireRateLimiting, and require sign-in. A store of your own can refuse a user who has used too much.
"I'll write outside the folder"
The trick: a folder name such as ../../.
The check: the folder path is normalized and resolved inside the upload root, and a path that leaves it is refused. The file's name is generated by the server, so it cannot overwrite a file you already have.
What is still yours to do
Three things the package cannot decide for you:
- Require sign-in. Add your authorization policy. Until you do, anyone who can reach the site can upload.
- Stop the upload folder from running code. Static files in ASP.NET Core are not executed, but on IIS make sure no handler runs in that folder.
- Keep the software current. No list makes a site permanently safe.