How to do it...

First, let's explore the problem. We'll start both the vulnerable and adversarial servers.

If on the command line, we are in the directory directly above the app and attacker we can start each server by referencing the folder:

$ node app/ 

And in another terminal window:

$ node attacker/ 

Now let's set up some local domains to proxy to our two servers, using devurl (which we installed in the Getting ready section).

In a third terminal window, we run the following:

$ devurl app.local http://localhost:3000 

And in yet another terminal window, we run the following:

$ devurl attacker.local http://localhost:3001

Next let's navigate our browser to http://app.local, and log in with the username dave and password ncb, this should result in the following profile screen:

The details show that the account number is 12345678 and the Sort code is 88-26-26.

Now let's open a new tab, and navigate to http://attacker.local:

While every instinct tells us not to click the button that says Everything you could ever want is only one click away, let's click it.

Now if we go back to the first tab and refresh, we'll find that the details now show account number 87654321 with sort code 11-11-11.

This attack would work even if the first tab (where we initially logged in) was closed. As long as the browser still has a session cookie, any other tab or window can submit POST requests as a logged in user.

Now let's fix it. Let's copy the app folder to fixed-app:

$ cp -fr app fixed-app 

In fixed-app/index.js we'll update the session middleware like so:

app.use(session({
secret: 'AI overlords are coming',
name: 'SESSIONID',
resave: false,
saveUninitialized: false,
cookie: {
sameSite: true
}
}))

Now let's stop the app server and run the fixed-app server:

$ node fixed-app/ 

Also, we need to restart the devurl proxy:

$ devurl app.local http://localhost:3000 

If we navigate the browser to http://app.local againĀ and login we'll see the profile screen as before. Opening a new tab at http://attacker.local and clicking the button should have no effect (which we can verify by refreshing the http://app.local tab, as before). We should also see a 403 Forbidden error in Chromes Devtools.

Browser Support
WARNING: The technique in this recipe is only supported in modern browsers, see http://caniuse.com/#feat=same-site-cookie-attribute for browser version support. In the There's more section we'll include a fallback technique that is essential to avoiding CSRF attacks for browsers that lack support for the SameSite cookie.