Andrew Kelley

Do Not Use bodyParser with Express.js

Note: this post has been edited to take into account TJ's diligent work in response to this.

I came across this Google+ post mentioning this StackOverflow post in which someone is quite wisely asking whether the express.js framework is secure enough to use for production applications.

This reminds me of one "gotcha" in particular that you could be bitten by if you're not careful.

All servers using express.bodyParser are vulnerable to an attack which creates an unlimited number of temp files on the server, potentially filling up all the disk space, which is likely to cause the server to hang.

Demonstration

This problem is extremely easy to demonstrate. Here's a simple express app:

var express = require('express');
var app = express();

app.use(express.bodyParser());
app.post('/test', function(req, resp) {
  resp.send('ok');
});

app.listen(9001);

Seems pretty innocuous right?

Now check how many temp files you have with something like this:

$ ls /tmp | wc -l
33

Next simulate uploading a multipart form:

$ curl -X POST -F foo=@tmp/somefile.c http://localhost:9001/test
ok

Go back and check our temp file count:

$ ls /tmp | wc -l
34

That's a problem.

Solutions

Always delete the temp files when you use bodyParser or multipart middleware

You can prevent this attack by always checking whether req.files is present for endpoints in which you use bodyParser or multipart, and then deleting the temp files. Note that this is every POST endpoint if you did something like app.use(express.bodyParser()).

This is suboptimal for several reasons:

  1. It is too easy to forget to do these checks.
  2. It requires a bunch of ugly cleanup code. Why have code when you could not have code?
  3. Your server is still, for every POST endpoint that you use bodyParser, processing every multipart upload that comes its way, creating a temp file, writing it to disk, and then deleting the temp file. Why do all that when you don't want to accept uploads?
  4. As of express 3.4.0 (connect 2.9.0) bodyParser is deprecated. It goes without saying that deprecated things should be avoided.

Use a utility such as tmpwatch or reap

jfromaniello pointed out that using a utility such as tmpwatch can help with this issue. The idea here is to, for example, schedule tmpwatch as a cron job. It would remove temp files that have not been accessed in a long enough period of time.

It's usually a good idea to do this for all servers, just in case. But relying on this to clean up bodyParser's mess still suffers from issue #3 outlined above. Plus, server hard drives are often small, especially when you didn't realize you were going to have temp files in the first place.

If you ran your cron job every 8 hours for instance, given a hdd with 4 GB of free space, an attacker would need an Internet connection with 145 KB/s upload bandwidth to crash your server.

TJ pointed out that he also has a utility for this purpose called reap.

Avoid bodyParser and explicitly use the middleware that you need

If you want to parse json in your endpoint, use express.json() middleware. If you want json and urlencoded endpoint, use [express.json(), express.urlencoded()] for your middleware.

If you want users to upload files to your endpoint, you could use express.multipart() and be sure to clean up all the temp files that are created. This would still stuffer from problem #3 previously mentioned.

Use the defer option in the multipart middleware

When you create your multipart middleware, you can use the defer option like this:

express.multipart({defer: true})

According to the documentation:

defers processing and exposes the multiparty form object as `req.form`.
`next()` is called without waiting for the form's "end" event.
This option is useful if you need to bind to the "progress" or "part" events, for example.

So if you do this you will use multiparty's API assuming that req.form is an instantiated Form instance.

Use an upload parsing module directly

bodyParser depends on multipart, which behind the scenes uses multiparty to parse uploads.

You can use this module directly to handle the request. In this case you can look at multiparty's API and do the right thing.

There are also alternatives such as busboy, parted, and formidable.