CSRF | Remove deprecated csurf
libary and replace with simple implementation
#3010
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What does this change?
The CSRF libary we were using in Gateway has long been deprecated (archived Sep 14, 2022, last published Jan 19, 2020): https://github.com/expressjs/csurf
Thankfully the risk was fairly minor as the protection still works, there just won't be any security updates to it. Their recommendation was to search npm for an alternate package should we need one: https://www.npmjs.com/search?q=express%20csrf
However going through the list of packages, there is no library which adequately replaces the same package. The closest was https://github.com/Psifi-Solutions/csrf-csrf, however that still had too many options that we didn't need.
Instead we decided to create our own custom implementation based on the OWASP Cheat sheet recommended pattern: Signed Double-Submit Cookie. We also used https://github.com/Psifi-Solutions/csrf-csrf as a reference too.
We made a similar API that the csurf library provided, so we don't have to make too much changes elsewhere in the Gateway project.
Implementation
For generating the hash:
For generating the CSRF token:
For validating the request:
Unlike the OWASP example, we don't have a session to bind the CSRF token to, which would lead to the "Naive Double Submit Cookie" pattern, with security implications mentioned in https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf
However we can avoid this pitfall by using signed cookies, which are less vulnerable to the same attacks, as they are signed with a (different) secret key, meaning that the attacker cannot forge a valid CSRF token, as any changes to the token would invalidate the signature, and the server wouldn't make the cookie available.
We also use the
__Host-
prefix to the cookie name where if a cookie name has this prefix, it's accepted in aSet-Cookie
header only if it's also marked with theSecure
attribute, was sent from a secure origin, does not include aDomain
attribute, and has thePath
attribute set to/
. In other words, the cookie is domain-locked. Also see https://archive.is/8hPYXWe also only support supplying the token in the request via
req.body
, using a header or query parameters is not supported, as we don't use this in Gateway.Tested
Notes
This could potentially be refactored out into it's own library in the future, especially if there's other projects at the Guardian (or elsewhere) that could benefit from this!