Cross Site Request Forgery (CSRF)
ID |
javascript.cross_site_request_forgery |
Severity |
high |
Resource |
Authentication |
Language |
JavaScript |
Tags |
CWE:352, NIST.SP.800-53, OWASP:2013:A8, PCI-DSS:6.5.1 |
Description
Cross-Site Request Forgery (CSRF) is a security vulnerability that occurs when a malicious actor tricks a user’s browser into performing unwanted actions on a trusted web application where the user is authenticated. It primarily exploits the trust that a web application has in an authenticated user’s browser.
It can lead to unauthorized actions being executed in a web application on behalf of the user, potentially compromising personal data, making unauthorized transactions, or performing administrative operations.
Rationale
CSRF exploits the trust that a web application has in the user’s browser. Essentially, the attacker crafts a request that the user’s browser sends to a web server where the user is authenticated.
The key vulnerability is the application’s inability to verify that the request originated from its own legitimate web pages and intentionally made by the user, rather than from a malicious site. This is why CSRF protections typically involve adding unique tokens to forms that the server can verify came from its own pages.
If the vulnerable application does not have the necessary protection, the attacker can execute arbitrary actions on the user’s behalf.
The following is an example of a cross-site request forgery vulnerability in an Express application:
var express = require('express');
var https = require('https');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var app = express(); // FLAW
app.use( cookieParser() );
app.use( bodyParser.urlencoded({extended: false}) );
app.get('/form', function(req, res) {
res.render('form', {});
});
app.post('/process', function(req, res) {
// ... sensitive operation ...
});
const sslOptions = { /* ... */ };
https.createServer(sslOptions, app).listen(8443);
Remediation
The most common protection strategy against CSRF is to use anti-CSRF tokens, to ensure that requests are originated from the legitimate user. The token should be generated on the server backend, and included in every form or request to the backend.
Additional protection techniques that do not seclude the need for anti-CSRF token:
-
Validate Referer Header: In some cases, checking the
Referer
orOrigin
headers can provide another layer of protection, ensuring that the request originates from the same site. -
Force User Interaction: Force significant actions to require additional confirmation steps, like re-authentication or other forms of verification.
-
SameSite Cookie Attribute: Use the
SameSite
attribute for cookies to prevent them from being sent in cross-site requests.
Implementing these strategies in your applications will significantly reduce the risk of CSRF attacks.
For Node applications there are many packages that provide support for CSRF protection. If you are using frameworks like express
or koa
, you can even register a middleware to handle the CSRF protection almost transparently.
Using an anti-CSRF package
There are many packages that can be used to protect against CSRF for different popular web frameworks.
For Express the most popular is (was) csurf which is now deprecated and forked into @sailshq/csurf. The tiny-csrf is almost a drop-in replacement for csurf. Another popular package is The csrf-sync package follows the Synchronizer Token Pattern.
For Koa the most popular is koa-csrf.
For Fastify the most popular is @fastify/csrf-protection.
For NestJS the @tekuconcept/nestjs-csrf package helps with creating interceptors that automate the process of generating CSRF tokens, and guards for validating them.
Using custom middleware
This example uses the csrf-csrf package following the Double Submit Cookie Pattern.
// File middlewares/csrf.middleware.js
const { doubleCsrf } = require('csrf-csrf');
const csrf = doubleCsrf({
// secret used for generating csrf token taken from environment
getSecret: () => process.env.CSRF_SECRET,
getTokenFromRequest: req => req.body.csrfToken,
cookieName: process.env.NODE_ENV === 'production' ? '__Host-prod.x-csrf-token' : '_csrf',
cookieOptions: {
secure: process.env.NODE_ENV === 'production' // Enable for HTTPS in production
}
});
module.exports = {
doubleCsrfProtection: csrf.doubleCsrfProtection,
generateToken: csrf.generateToken
};
Then import the middleware into your app.js
so that the middleware is registered globally:
// File app.js
const csrfMiddleware = require('./middlewares/csrf.middleware.js');
app.use(doubleCsrfProtection);
app.use((req, res, next) => {
res.locals.csrfToken = generateToken(req, res);
next();
});
The local csrfToken
is available to views in template rendering.
So for each form that needs to have the CSRF token injected (so POST will validate the CSRF token), simply add the following:
<input type="hidden" name="csrfToken" value="{{csrfToken}}">
Now the form submit is properly handled instead of throwing a 403 (which it will as soon as the global doubleCsrfProtection
middleware is enabled).
References
-
CWE-352 : Cross-Site Request Forgery (CSRF)
-
OWASP - Top 10 2021 Category A01 : Broken Access Control.
-
Cross-Site Request Forgery Prevention Cheat Sheet, in OWASP Cheat Sheet Series.
-
What’s the problem with the csurf package? - Technical advice on how to implement the Double Submit Cookie pattern well, and to avoid common weaknesses in the pattern.