What’s the problem with the CSURF package?

The csurf library is one of the most popular libraries to carry out the task of CSRF protection in the Node ecosystem. By the time of writing the library had 400K downloads per month. So, it is part of many projects. But, a vulnerability behind it was rediscovered recently by Adrian Tiron from the Fortbridge Team (you can check out his write-up). The library has several wrong designs and could be a counterintuitive library to protect us against CSRF attacks.

Table of Contents

Rediscovered? What does it mean? Let’s talk about it. It is a fact that some developers base their judgment about the functionality of a library on the number of stars, downloads, fame, or documentation, which in some cases means respect and trust. But it is possible to find some issues even if the library is famous and widely used. We take this news seriously (as a part of the research team of dev-academy.com) because this problem could be dangerous for Node (Express.js) applications, and together with Bartosz, we’ve found some thoughts about how you can avoid it.

In the case of csurf, we’ve found that its vulnerabilities and design problems were discovered by David Johansson 5 years ago (read his presentation and additionally check out his talk).

At the moment of redaction, veryriskyrisk comes to the scene with his PoC code (19th October 2022), in which he exploits the csurf library even if the cookies are signed. It is simple, it works, and it is so crazy. Enjoy this article (it was rethought many times 😉), and let’s get started.

The library issues

On dev-academy.com, we encourage you to read David Johansson’s presentationAdrian Tiron’s write-up, and veryriskyrisk’s PoC code to understand, test, and exploit the vulnerability in depth. But we can keep and talk about their issues’ summary by importance.

  • The csurf library accepts as valid a properly spoofed pair of _csrf cookie and the actual csrf token in any vulnerated instance (injected via subdomain cookie tossing or cross-site scripting).
  • No signature checking for cookies by default.
  • Double Submit Cookie Pattern implementation based on partially incorrect assumptions, the csurf package uses _csrf cookie as a state for a pattern that is stateless itself.
  • sha1 is deprecated (used by csrf, a csurf’s dependency).

Above issues are related to the stateless approach of csurf (the Double Submit Cookie Pattern, csrf({ cookie: true }), so its stateful implementation (csrf({ cookie: false }) seems to be safe.

Let’s start with the light part of the facts. sha1 was deprecated in 2011 (many years before). It was used until 2017 as an algorithm for digital signature purposes on browsers like Firefox and Chrome. So its use in 2022 is dangerous.

The _csrf token is not the only token generated as Double Submit Cookie Pattern usually suggests. It has extra data used as a state persistence method for a stateless approach (with secret and salt, which are stored in plain text).

What’s the big problem? Following Adrian Tiron’s write-up and some attempts to get the same result, we reconfirmed that the library has several issues with its internal validation logic.

Shortly, when you request a route (sign-in form as an example), a cookie called _csrf is installed in the response, that cookie is up to you, but later it will be used as a part of the POST request at the sign-in form along to a csrf token generated by a secret. That csrf token often is rendered in the HTML response as a hidden field.

The mindblowing part is that the csurf library uses the _csrf cookie as a secret to validate if the csrf token (passed in many ways) matches. The csrf token is generated by a secret, but if you have your secret and a csrf token generated by that secret, then the validation will be accepted. The trick is: how to do it in real life? The answer is the cookie-tossing technique or cross-site scripting. Using it in a subdomain allows you to insert your _csrf cookie and get over the genuine _csrf cookie. The remaining work is how to send the csrf token easily. csurf accepts many forms (body, header, query params, and cookies). The easiest way? Query params. The rest is a victory, but this exploitation only works with the default configuration (app.use(csrf({ cookie: true }));). The following case is crazy too.

At first, we thought that signing the cookies was the short answer to this problem because the default configuration does not have it enabled, but veryriskyrisk confirmed the opposite situation. If you need visual proof, watch this crazy PoC video exploiting the csurf and cookie signing issues.

Shortly, the video and PoC repository shows how you can exploit the csurf package even if the cookies are signed. How is it possible?
Before all, the attacker’s website should have the cookie-signature package (locally at node_modules) changed or manipulated at line 42 to bypass the cookie signature verification. First, we need to steal a valid _csrf cookie of a victim website, toss it at the attacker’s website and generate a csrf token pair on that side (attacker’s website) with the manipulated version of the cookie-signature. Simple as it is.

These issues reveal the library and libraries (in general) don’t pretend to be a complete solution to fight against CSRF attacks. Keep in mind some extra steps in the server-side configuration or replace csurf completely. We talk about it in some captions below.

The source code never lies

As developers in a back-end role, sometimes we delegate the weight of complex mechanisms implementation to popular libraries in the ecosystem. Maybe it resolves the problem and saves a lot of time, but in the process, we can introduce issues and vulnerabilities in our applications.

The packages are great! But, it has people behind it and needs more of them to create a good one, so the contribution is crucial. Every package could work well, but you don’t take this as a source of truth (doubts and multidimensional perspectives are welcome in the contribution ecosystem :D), and the popular package csurf is one of the multiple probes of it.

Check out the source code always, if it is possible. Maybe take a little or more time than you have to implement functionality, but this practice is a way to improve your skills and keep your system secure. A usual path for cyber-criminals to take advantage of each package ecosystem is when the dependency installation is underway or when the library has unintentional issues.

How works the Double Submit Cookie pattern?

There are many ways to achieve a DSC pattern correctly, and we think the following graphic explains a usual case.

csurf package overview

The implementation depends on your app architecture, but the DSC pattern is stateless and is one of the most straightforward techniques (that’s where its popularity lies). But the code implementation alone is not 100% secure. The trick is in your low-level web tools (cookies settings, HTTP Strict-Transport-Security, Cross-origin Resource Sharing, Same-Origin Policy, and security headers in general).

Then, what can I do?

In the case of the csurf package, the creator decided to deprecate this package, which means we need to search for alternatives or implement the “Double Submit Cookie” technique on our own. So we won’t have new versions in the short term.

csurf package overview

So we have these recommendations for you:

  • Prefix your cookies with __Host-. This prefix doesn’t allow subdomains or other domains to overwrite your cookies if they don’t set the cookie previously.
  • Use the HttpOnly and Secure configuration in your cookies. These flags don’t allow JavaScript to access your cookies (avoid XSS vector in the way too) and hide the cookie if the request does not go via HTTPS.
  • Use custom headers and cookie names (not x-csrf, _csrf, csrf, x-csrf-token…).
  • Use the Strict-Transport-Security header. This header tells you the browser that always uses HTTPS instead of HTTP as the main protocol of communication.
  • Find an alternative library on NPM or use our PoC repository as a guide to replace the csurf library using the csrf-csrf library on NPM.
  • Implement one of the multiple techniques to mitigate the CSRF vector (on your own).

Weeks ago, we found the csrf-csrf library to replace csurf in express projects. This library is relatively new. It has a modern code implementation, powered by the Typescript ecosystem. So we can use it by calling the doubleCsrf function, as we see below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import express from 'express';
import { doubleCsrf } from 'csrf-csrf';
import cookieParser from 'cookie-parser';

const app = express();
app.use(express.json());

const {
invalidCsrfTokenError,
generateToken,
doubleCsrfProtection
} = doubleCsrf({
getSecret: (req) => req.secret,
secret: CSRF_SECRET,
cookieName: CSRF_COOKIE_NAME,
cookieOptions: { sameSite: false, secure: false, signed: true }, // not ideal for production, development only
size: 64,
ignoredMethods: ['GET', 'HEAD', 'OPTIONS'],
});

app.use(cookieParser(COOKIES_SECRET));

Once you have the initial configuration according to your need (enable the most secure configuration at production), you can use it as a middleware in your routes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Error handling, validation error interception
const csrfErrorHandler = (error, req, res, next) => {
if (error == invalidCsrfTokenError) {
res.status(403).json({
error: 'csrf validation error'
});
} else {
next();
}
};

app.get('/csrf-token', (req, res) => {
return res.json({
token: generateToken(req, res)
});
});

app.post('/protected_endpoint', doubleCsrfProtection, csrfErrorHandler, (req, res) => {
console.log(req.body);
res.json({
protected_endpoint: 'form processed successfully'
});
});

// Try with a HTTP client (is not protected from a CSRF attack)
app.post('/unprotected_endpoint', (req, res) => {
console.log(req.body);
res.json({
unprotected_endpoint: 'form processed successfully'
});
});

The middleware validates the token in headers and cookies received in the post requests. Use the csrfErrorHandle as a csrf error validation interceptor because the default errors are very transparent to the user (revealing the complete stack trace).

In the PoC repository, you can play with the html file (client side) and its script to change the validation behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anti-CSRF Example</title>
</head>
<body>
<div id="app"></div>
</body>
<script type="module">
// Don't forget to change the port if the server uses another one.
const PORT = 3000
const response = await fetch(`http://127.0.0.1:${PORT}/csrf-token`);
const { token } = await response.json();
console.log('the token', token);

// The csrf cookie was implicit set on the request by the server
const post = await fetch(`http://127.0.0.1:${PORT}/protected_endpoint`,{
method: 'POST',
headers: {
'x-csrf-token':token, // comment this line to throw an error.
'Content-Type':'application/json',
},
body: JSON.stringify({
name:'John Doe',
id:'xasd2312x2ñljkasdas'
})
})
const app = document.getElementById('app');
const data = JSON.stringify(await post.json());
app.innerHTML = `<code>${data}</code>`
</script>
</html>

Summary

We won’t encourage you to untrust every little package that you have. But, the packages related to authentication, authorization, routing, and user input must be taken seriously. Take this problem as a challenge to test libraries, form part of the contribution of a single one like csrf-csrf (how I added this example), or implement your super secure anti-csrf library! In Web Security Academy, we have a detailed explanation of the CSRF attacks, do not miss the opportunity to learn.

Until another post. Says goodbye your #TechFriend @cr0wg4n.

Subscribe to Dev Academy

Join over 6000 subscribers that receive latest knowledge and tips!

    By submitting this form you agree to receive emails with news, promotions and products and you accept Privacy Policy.