Can an uploaded text file open as a page? Add nosniff

The X-Content-Type-Options: nosniff header tells the browser to follow the Content-Type your server sent instead of guessing the type from the content, so a text/plain file with HTML in it stays text.

Published 2 min read#App security#Web development

A user uploads a file called notes.txt to your site, and inside it are lines of HTML and a script. Your server serves it from the same domain, and if the browser decides it looks like a page, you now have a page you never wrote running under your site's name. This article explains why the browser may guess, and how to stop it with one header.

What is MIME sniffing in the browser?

MIME sniffing is the browser reading the content of a response to decide its type, instead of following the Content-Type header your server sent.

OWASP's HTTP Headers Cheat Sheet says this sniffing can transform non-executable MIME types into executable MIME types. In other words, a text file or an image may be handled by the browser as something it runs.

What does X-Content-Type-Options: nosniff do?

X-Content-Type-Options is a response header that tells the browser the MIME types in the Content-Type header should be respected and not changed, and it has one value: nosniff.

X-Content-Type-Options: nosniff

According to MDN's X-Content-Type-Options page, the header does three things:

  1. It blocks a request if its destination is style and the type is not text/css, or if its destination is script and the type is not a JavaScript MIME type.

  2. It prevents sniffing for all other response types, so the browser uses the declared Content-Type without examining the content.

  3. It stops the browser from treating a response as text/html when it is loaded in a browsing context and the Content-Type is absent or not an HTML type.

How does the header protect uploaded files?

The header protects uploaded files because the browser shows them by the type your server sent: a text file stays text, even when it holds HTML.

The same MDN page gives this exact example: if a server sends a response with Content-Type: text/plain and X-Content-Type-Options: nosniff, the browser will not interpret it as HTML, even if the content contains HTML markup:

Content-Type: text/plain
X-Content-Type-Options: nosniff

<script>alert(document.domain)</script>

In the browser this response shows up as a line of text, and the script does not run.

Why does Content-Type have to be right too?

Content-Type has to be right because nosniff makes the browser take it literally: a wrong type on a CSS or JavaScript file makes the browser block it.

OWASP's recommendation is two things together: set the Content-Type header correctly throughout the site, and add X-Content-Type-Options: nosniff. The same cheat sheet says the charset attribute is necessary to prevent XSS in HTML pages, with this example:

Content-Type: text/html; charset=UTF-8

And the MDN page says site security testers usually expect this header to be set, and the Content-Type header to be correct for all resources.

Where do you start now?

Open a file a user uploaded to your site, plus one CSS file and one JavaScript file, and look at the response headers in the developer tools. If X-Content-Type-Options: nosniff is missing, add it to every response on the server, and check that each file goes out with its correct type before you ship, so a CSS file that was served with the wrong type does not stop loading.

If you are working through your security headers, see CSP in Report-Only mode to limit who can run scripts on your page, and setting up HSTS correctly. If you want a full test of your file uploads and the rest of the platform, Muhassan does that.

Does your site accept files from users? What type does it serve them back with?

FAQ

Is nosniff enough on its own?

No. nosniff makes the browser follow the Content-Type you sent, so Content-Type has to be correct for every file on the site, which is what OWASP recommends.

What happens to a CSS file served with the wrong type?

With nosniff, the browser blocks a style request whose type is not text/css, and a script request whose type is not a JavaScript MIME type.

Why does OWASP recommend a charset on HTML pages?

The OWASP cheat sheet says the charset attribute in Content-Type is necessary to prevent XSS in HTML pages, and its example is text/html; charset=UTF-8.

Share

More articles

  • 3 min read#App security#Privacy

    Is your password reset link leaking? Set Referrer-Policy

    Browsers send your page address with requests in the Referer header. The Referrer-Policy header decides how much of it goes out, and no-referrer removes it from sensitive pages.

  • 3 min read#AI#Developer tools

    Know the size of your Claude prompt before you send it

    The count_tokens endpoint in the Claude API tells you how many input tokens a request has before you send it, in the same shape as a message request. Counting is free, with its own rate limit.

  • 3 min read#Web development#Developer tools

    Still grouping arrays with reduce? Use Object.groupBy

    Object.groupBy splits any array into groups in one line, based on a value a callback returns, and gives you an object with no prototype. When the key is an object rather than a string, use Map.groupBy.