Going surfing – Protect your Node.js app from Cross-Site Request Forgery

January 11, 2018
Written by

mRMN3UYfzX8BjZ-_AVS4s_fF9250cHvOqDncnx3omhfqvNiYVg2VldUd5IBJTnI-Gss2wN5UpkCwxi0MgkNOuoKtLS2m41LsK6u0QHcl3n_rZIja6s0flrEEDS4wwWWVSlkjyiY0

One classic attack when working with web applications is Cross Site Request Forgery aka CSRF/XSRF (read C-Surf). They are used by attackers to perform requests on behalf of users in your application without them noticing. Let’s look at how they can pull this off and how we can protect our applications from these type of threat.

Let’s talk theory

Before we can prevent CSRF attacks we need to understand how they work. Typically these attacks are executed on the functionality of web applications that use form-based submissions like POST requests and cookie-based authentication.

An attacker places a hidden form into their malicious page that automatically performs a POST request to your page’s endpoint. The browser then automatically sends all the cookies stored for that page along with the request. If a user is logged into a current session, the attacker could, for example, post a message on behalf of the logged in user without them noticing. The attacker never has to have access to the page’s cookies for this.

We can protect ourselves from this attack by using CSRF tokens. The concept is that when the browser gets a page from the server, it sends a randomly generated string as CSRF token as a cookie. Later, when your page performs a POST request it will send the CSRF token as a cookie and also in another way such as a parameter in the body or via an HTTP header like X-CSRF-Token.

An attacker will not be able to reproduce the same behavior with their hidden form since they won’t be able to access the cookie to retrieve the value and send it along with their malicious POST request.

This concept can be implemented in pretty much any web application but let’s look at how we can implement it in an Express application.

Getting the board ready

First we need an application to see how the CSRF vulnerability works in reality and how we can protect ourselves from it. If you already have an existing Express application, feel free to perform the following steps on it. Alternatively follow the next steps to set up our demo application.

Before we get started make sure you have Node.js and npm or another package manager installed. Start the new project by running the following commands in your terminal:

mkdir csrf-demo
cd csrf-demo
npm init -y
npm install express body-parser --save

Next create a new file called index.js and place the following code into it:

const express = require('express');
const bodyParser = require('body-parser');

const PORT = process.env.PORT || 3000;
const app = express();

app.use(bodyParser.urlencoded({
  extended: true
}));

app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <form action="/entry" method="POST">
      <div>
        <label for="message">Enter a message</label>
        <input id="message" name="message" type="text" />
      </div>
      <input type="submit" value="Submit" />
    </form>
  `);
});

app.post('/entry', (req, res) => {
  console.log(`Message received: ${req.body.message}`);
  res.send(`Message received: ${req.body.message}`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

Start up the application by running:

node .

Visit http://localhost:3000 and you should be greeted with Hello World and a small form below it.

Dangerous Water

The current server has two endpoints. One is our main page that is served when you go to http://localhost:3000/. The other one is a POST endpoint on http://localhost:3000/entry. When we fill out the form and press Submit we’ll make a POST request to this endpoint.

Try it by entering some text in the form and press submit. You should see the message returned and it should also be logged into the console of your running server.

Unfortunately an attacker is able to perform the same request on their page. To simulate that we implemented the same form on a page on Glitch. Visit csrf-attack.glitch.me, type in a message and press submit. The behavior will be the same as submitting the form on the localhost page. It will transfer the message and along with it any cookies that are set.

In this case we created a form that the user can submit by themselves but it could have been a hidden form that auto submits with malicious content. Let’s see how we can protect our page from this.

Going csurfing

There are multiple modules that help you implement CSRF tokens in your application. One of them is csurf. Install that module along with the cookie-parser dependencies by running:

npm install cookie-parser csurf --save

Both of these modules are middleware that can alter the behavior of a request in Express. We are already using body-parser to parse our POST body to retrieve the message. Additionally we’ll use it to check for the _csrf token. The cookie-parser middleware will check that the token is available in the cookies and csurf will be the automatic guard for any POST, PUT, PATCH or DELETE operations by checking that the _csrf token is present in both the cookies and the request body and that they match.

Add the following code to your index.js file to configure the middleware:

const express = require('express');
const bodyParser = require('body-parser');
const csurf = require('csurf');
const cookieParser = require('cookie-parser');

const PORT = process.env.PORT || 3000;
const app = express();

const csrfMiddleware = csurf({
  cookie: true
});

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(cookieParser());
app.use(csrfMiddleware);

app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <form action="/entry" method="POST">
      <div>
        <label for="message">Enter a message</label>
        <input id="message" name="message" type="text" />
      </div>
      <input type="submit" value="Submit" />
      <input type="hidden" name="_csrf" value="${req.csrfToken()}" />
    </form>
  `);
});

app.post('/entry', (req, res) => {
  console.log(`Message received: ${req.body.message}`);
  res.send(`CSRF token used: ${req.body._csrf}, Message received: ${req.body.message}`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

Restart your server and navigate to http://localhost:3000. Enter some text in the input box and hit Submit. You should see the message appear in the console and be greeted in the browser with a message like the one below:

Screen Shot 2017-11-02 at 4.14.02 PM.png

Now switch back up to the demo page on Glitch and enter a message there. When you hit submit you’ll see that the request failed and that the message won’t appear in the console. The _csrf cookie is transferred, however, the page doesn’t send the same value in the POST body as _csrf value. As a result, the request is blocked by the csurf middleware and we have protected ourselves from CSRF attacks.

What’s next?

We’ve seen how to easily integrate CSRF tokens into a Node.js based application with server-side rendered code. However, using CSRF tokens with your front-end frameworks and libraries is just as easy. Since we are sending the token as a cookie, you can just as easily read it and send it as a header with your async requests later. In fact Angular’s HttpClient has this feature already built-in.

To learn more about how you can secure your Node.js applications make sure to check out my blog post about Securing your Express app. Additionally you should check out the OWASP page as it covers a wide range of security related topics.

If you have any questions or any other helpful tools to improve the security of your Node.js web applications, feel free to ping me: