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 presentation, Adrian 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 bycsrf
, 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.
There are many ways to achieve a DSC pattern correctly, and we think the following graphic explains a usual case.
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.
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
andSecure
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 | import express from 'express'; |
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 | // Error handling, validation error interception |
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 |
|
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.
Comments