CSRF Example

An overview of CSRF, SOP, & CORS

Cross-Site Request Forgery is an attack that allows an attacker to make authenticated calls to any websites the victim may be logged into. Here we run through an example scenario.

Follow code on Github: https://github.com/mohammedabdulwahhab/csrf_examplesarrow-up-right

Victim Webserver

Let's suppose the victim wants to log into his banking website. Our (simple) banking webserver has a GET endpoint for the home page and a POST endpoint for transfers. We use express to create this bank webserver.

HI
const express = require('express')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const app = express()
const port = 3000

// We need cookies and body params
app.use(cookieParser())
app.use(bodyParser())

// Middleware function to authenticate into the webserver
function is_authenticated(req, res, next) {
  console.log('Came here')
  // Check cookies to determine if logged in
  if (req.cookies['session'] == 'secret_token_placed_here'){
    next()
  }
  else{
    res.redirect('/auth')
  }
}

app.get('/auth', (req, res) => {
  console.log('Authenticating', req.query.username, req.query.password)
  // User has valid login
  if (req.query.username == 'username' && req.query.password == 'password'){
      // Add cookie certifying user has authenticated
      res.cookie('session', 'secret_token_placed_here', {
        maxAge: 1000 * 60 * 15, // would expire after 15 minutes
        httpOnly: false, // The cookie only accessible by the web server/not javascript
        signed: false // Indicates if the cookie should be signed
      })
      res.redirect('/')
  }
  // Login was bad
  else {
    res.set('Content-Type', 'text/html');
    res.status(200).send('Click <a href="/auth?username=username&password=password">here to login</a>')
  }
})


app.get('/', is_authenticated, (req, res, next) => {
  console.log('Logged into banking website')
  res.set('Content-Type', 'text/html');
  res.status(200).send("<h3>Welcome to your bank!</h3><form action='/transfer' method='post'><input id='POST-name' placeholder='recipient' name='recipient'><input id='POST-amount' placeholder='$20' name='amount'><input type='submit' value='Save'></form>")
})

app.post('/transfer', is_authenticated, (req, res) => {
  console.log('Transferring money to', req.body.recipient, req.body.amount)
  res.set('Content-Type', 'text/html');
  res.status(200).send('<h3>Success! Go back to <a href="/">home</a></h3>')
})


app.listen(port, () => console.log(`Bank app listening on port ${port}!`))

Serve this webserver with node bank.js and check that everything works:

GET /
POST /transfer

Great! Our bank works as expected. Now let's see how a hacker could exploit our design to transfer money to himself.

Attacking Webserver

Let's take a look at how our bank checks that requests are originating from an authenticated user.

  1. User Logs in: When the user logs in, the server asks that the browser stores a cookie named session. We see this happen in the handler for the /auth route

2. User makes request:The GET '/' & POST '/transfer' routes are protected by the is_authenticated middleware. This middleware checks that the HTTP request came with a valid session cookie. If not, the server redirects them to GET /auth

This authentication strategy works because the browser always sends the cookies set by an origin. In this case, any request to localhost:3000 will always have all the cookies set by that origin sent along by the browser. This property of cookies is exactly what a CSRF attack exploits to cause state changes.

In this case, any request to localhost:3000 will always have all the cookies set by that origin sent along by the browser. This property of cookies is exactly what a CSRF attack exploits to cause state changes.

All the attacker needs to do now is to trick a victim into clicking on a link to a different webpage. Once this happens, a form on that webpage could easily POST to the bank web-server. The browser will send along the session cookie stored in the browser. This type of request is indistinguishable from one intentionally initiated by the user from the perspective of the bank web server and thus succeeds.

Let's construct this attacking webserver

Run the webserver with node attacker.jsNavigate to localhost:5000 to see our attack in action.

Protection against CSRF

The most common protection measure against this vulnerability is to enrich the form with a hidden token that is submitted along with the form. The server must do the added work of checking that this token is valid, but this will prevent against the CSRF attack. Let's see it in action. We modify the GET & POST routes in bank.js to include a csrf_token from the session

If we navigate to localhost:5000 we see that our attack no longer works. Success!

What if the attacker used AJAX to get the hidden csrf_token?

This is a good question. What if the attacker simply included some AJAX in his webpage to retrieve the csrf associated with the session. Why is he not able to do this?

This brings us to the Same Origin Policy (SOP) of browsers. SOP prevents sites from making AJAX requests across origins. This means the webpage would not succeed in making the AJAX call mentioned previously. Let's add this to our attacker webpage script:

We see that the request is denied. What is interesting to note is that the request still goes through (check this in the bank webserver) logs, but the response is blocked from the requestor.

CORS

Finally, we want to lax this policy in certain situations. For example, a REST API needs to be accessed across origins. We can permit this behavior by adding a Cross Origin Request Sharing header to our API. In express, the ''cors' package allows us to easily enable this.

https://stackoverflow.com/questions/7067966/why-doesnt-adding-cors-headers-to-an-options-route-allow-browsers-to-access-myarrow-up-right

Last updated