Don't Sniff Mimetype

Written by Ulises Gascón

Apr 08, 20202 min read

The attack

MIME types are a way of determining what kind of file you’re looking at. PNG images have the type image/png; JSON files are application/json; JavaScript files are typically text/javascript. When your browser loads a file, it reads the server’s Content-Type header to determine what the thing is.

Let’s say that your browser sees this:

<script src="https://example.com/my-javascript"></script>

It’ll go and load my-javascript from example.com. If example.com sends a Content-Type header of text/javascript, your browser will execute the contents of my-javascript as JavaScript.

But what if my-javascript is an HTML page with a Content-Type of text/html? If your browser does something called “MIME sniffing” (which some do), it will look at the contents of the file, decide if it looks like JavaScript, and execute it if so. This means that a server can send the wrong Content-Type and JavaScript can still get executed.

This MIME sniffing can be an attack vector. A user could upload an image with the .jpg file extension but its contents are actually HTML. Visiting that image could cause the browser to “run” the HTML page, which could contain malicious JavaScript! Perhaps the nastiest attack is called Rosetta Flash, which allows someone to load a malicious Flash plugin instead of data!

The header

The X-Content-Type-Options header tells browsers not to sniff MIME types. When this header is set to nosniff, browsers won’t sniff the MIME type—they will trust what the server says and block the resource if it’s wrong.

The code

The noSniff middleware will set the X-Content-Type-Options header to nosniff for every request.

const helmet = require('helmet')

//By default: Sets "X-Content-Type-Options: nosniff".
app.use(helmet())

// Sets "X-Content-Type-Options: nosniff".
app.use(helmet.noSniff())

Refs: