Skip to main content

Secure file upload in ASP.NET Core: the checks that matter

The checks an upload passes on the server before it is saved

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:

Read next