Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rfc: CSRF protection #879

Merged
merged 7 commits into from May 21, 2024
Merged

rfc: CSRF protection #879

merged 7 commits into from May 21, 2024

Conversation

ematipico
Copy link
Member

@ematipico ematipico commented Apr 2, 2024

Summary

CSRF - Cross-site Request forgery - protection

Links

@ematipico ematipico mentioned this pull request Apr 2, 2024
@florian-lefebvre
Copy link
Member

I wonder if it would be better to make the option available under security, in case we want to add more security features (eg. honeypot) to core. So

// astro.config.mjs
export default defineConfig({
  security: {
    csrfProtection: ["origin", "cookie"]
  }
})

@natemoo-re
Copy link
Member

@florian-lefebvre that's a good suggestion. We do try to avoid nesting too early since we don't necessarily know if we'll ever add something like a security.honeypot option, but if we already see lots of potential for expanding the APIs, adding a top-level category is a good suggestion.

@florian-lefebvre
Copy link
Member

I think CORS could be supported in the future as well but i can't see anything else rn

@ematipico
Copy link
Member Author

@lilnasy do you envision some option for the CSP support? If so, we could add a top-level security option.

@lilnasy
Copy link
Contributor

lilnasy commented Apr 2, 2024

Not necessarily. It's too soon to say because it's only stage 1, but it's possible a solution could just work. I only envision a flag for backwards compatibility, and that's only if the picked solution can't be retrofitted.


# Non-Goals

- Give the users the possibility to customise the implementation of the protection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume static support would be a non-goal here? We can only add CSRF protection to the routes that are handled by the Astro server, right?

Copy link
Contributor

@lilnasy lilnasy Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and the threat of CSRF is only relevant to data mutations, so I would think that this is okay.


Most background is available here: https://owasp.org/www-community/attacks/csrf

Astro should provide some level of security to users.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love to discuss this! Obviously we should, but can we get more specific about what level of security we are able to provide and what must be left up to users? This RFC seems like a good reason to have that larger conversation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not good at writing, so if you have a better suggestion, I would really appreciate it


# Goals

- Add the required checks to prevent CSRF, probably via an option
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect a bit more detail here!

Here are some other potential goals to consider that may (not) be relevant:

  • Automatically apply CSRF protection to all routes
  • Allow users to configure CSRF protection per-route
  • Enable CSRF protection by default (eventually)
  • Only pages/, or API endpoints too?
  • Only server, or hybrid too?

Copy link
Member Author

@ematipico ematipico Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed two commits:

I wouldn't want to enable CSRF by default now because it could break users's applications. For the future, if we want to make it a default, I think it should require a separate discussion, outside of this RFC.

Comment on lines 40 to 55
During the exploration phase, I found two ways to implement CSRF.

## `"origin"` option

The header `origin` is a header that is preset and set by all modern browsers and there's no way to temper it. If the header `origin` won't match
the origin of the URL, Astro will return a 403.

This solution should fit most websites.

## `"cookie"` option

The cookie solution is an alternative way to provide CSRF protection. It will be heavily inspired from [Angular](https://angular.io/guide/http-security-xsrf-protection).

When the first `GET` request to the application is sent, Astro will create a token that will be saved inside a cookie named `Astro-csrf-token`. This token will be read in the **known requests**.

This solution should fit more esoteric scenarios, where applications are behind reverse proxies.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems more like background than implementation. Can we automatically match the origin or does it require a user to configure the site? How would the cookie be generated? Is the cookie static or refreshed for each build?

@ematipico
Copy link
Member Author

In this commit 4d37d51 (#879) I changed the shape of the configuration due to some requirements that are needed for the cookie/token strategy.


The cookie solution is an alternative way to provide CSRF protection. It will be heavily inspired from [Angular](https://angular.io/guide/http-security-xsrf-protection).

When the **first** `GET` request is sent to the application, Astro will create a token that will be saved inside a cookie named `Astro-csrf-token`. This token will be read in the **known requests** and it doesn't match the expected value, it means that the request isn't genuine.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the **known requests**? Which requests are these that it's checked?

Copy link
Member Author

@ematipico ematipico Apr 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are mentioned in this paragraph, third sentence.


When the **first** `GET` request is sent to the application, Astro will create a token that will be saved inside a cookie named `Astro-csrf-token`. This token will be read in the **known requests** and it doesn't match the expected value, it means that the request isn't genuine.

The token must be unique for each user and must be verifiable by the server; his prevents the client from making up its own tokens.
Copy link
Contributor

@lilnasy lilnasy Apr 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how angular's approach works.

  • From the sound of it, it seems stateful. Who writes the state, who reads the state, where does it go?
  • The angular docs make mention of setting a custom header, implying they manage the client-side code that makes the request. Is astro going to offer a client-side fetch() wrapper?

A few implementation details may help communicate the approach.

export default defineConfig({
security: {
csrfProtection: {
cookie: ["cookie-name", "token-name"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It comes across as odd that the token "name" is configurable because I don't think the token was introduced as a key-value pair.

Astro will create a token that will be saved inside a cookie named Astro-csrf-token.

If "Astro-csrf-token" is the cookie name, the token is the value of that cookie, what's a token name?

@septatrix
Copy link

septatrix commented Apr 12, 2024

This will break form submissions when using curl for example or pretty much any other tool which is not a modern browser such as http clients in most programming languages. Another example would be load-testing tools. While some like k6 support setting custom headers this is cumbersome and not supported by everything.

A cookie based implementation would at least work with everything which has a sense of session. So not curl but at least the other stuff I listed.

Originally posted by @septatrix in withastro/astro#10678 (comment)

@ematipico
Copy link
Member Author

@septatrix: have you read the RFC? The RFC proposes a cookie solution, it would be great if you could share your thoughts about that.

@matthewp
Copy link
Contributor

@septatrix can you explain why this will break curl usage? Does curl not allow you to sit the origin header?

@septatrix
Copy link

Curl does allow setting headers but this has to be done manually, existing automation might fail and - as said above - this is not the case for everything. E.g. many load testing tools are very simplistic and do not allow setting headers.

The cookie solution would still require changes to existing curl scripts and require one additional initial request to fetch the cookie. However, cookies are automatically handled by many other tools, especially client libraries in many programming languages establish session which store cookies between requests.

@septatrix
Copy link

One might also consider adding a configuration to the origin checking of how to treat requests with missing origin headers. To my knowledge this would apply to old browsers and most of non-browser tools. This would allow tools like curl et al to keep working while omitting the security checks for the fraction of users running very old browsers

ematipico and others added 2 commits May 14, 2024 08:42
Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com>
@ematipico
Copy link
Member Author

We decided to reduce the scope of the protection to the origin check only, hence the proposed configuration is also reduced. Changed applied in bc87ea7 (#879)

Also, this is a call for consensus! The RFC is ready to be shipped.

@florian-lefebvre
Copy link
Member

Is it possible to opt-out of CSRF on a route basis? For example for webhooks like stripe

@ematipico
Copy link
Member Author

Is it possible to opt-out of CSRF on a route basis? For example for webhooks like stripe

No, it isn't possible at the moment. Do you have webhooks that send posts like they were forms?

@florian-lefebvre
Copy link
Member

Not sure I understand what you mean. In the case of stripe, a webhook is a post route that accepts some data (through request.text()). Stripe docs recommend disabling cors for the webhook specifically

@ematipico
Copy link
Member Author

I understand now. I think it's a legitimate request, however, It can be implemented after this RFC lands

@florian-lefebvre
Copy link
Member

florian-lefebvre commented May 20, 2024

I created a RFC so we don't don't forget #923

@matthewp
Copy link
Contributor

The final comment period has ended and this RFC is accepted.

@matthewp matthewp merged commit 3f466ee into main May 21, 2024
@matthewp matthewp deleted the feat/csrf-protection-rfc branch May 21, 2024 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Implemented
Development

Successfully merging this pull request may close these issues.

None yet

6 participants