diff --git a/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md b/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md index 17bae843ea..b8715fe25f 100644 --- a/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md +++ b/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md @@ -14,7 +14,8 @@ In short, the following principles should be followed to defend against CSRF: - **See the OWASP [XSS Prevention Cheat Sheet](Cross_Site_Scripting_Prevention_Cheat_Sheet.md) for detailed guidance on how to prevent XSS flaws.** - **First, check if your framework has [built-in CSRF protection](#use-built-in-or-existing-csrf-implementations-for-csrf-protection) and use it** -- **If the framework does not have built-in CSRF protection, add [CSRF tokens](#token-based-mitigation) to all state changing requests (requests that cause actions on the site) and validate them on the backend** +- **If the framework does not have built-in CSRF protection, add [CSRF tokens](#token-based-mitigation) to all state changing requests (requests that cause actions on the site) and validate them on the backend, or validate [Fetch Metadata headers](#fetch-metadata-headers) on the backend for all state-changing requests.** +- **If your software is intended to be used only on modern browsers, you may rely on [Fetch Metadata headers](#fetch-metadata-headers) to block cross-site state-changing requests, so long as you use the fallback options detailed below.** - **Stateful software should use the [synchronizer token pattern](#synchronizer-token-pattern)** - **Stateless software should use [double submit cookies](#alternative-using-a-double-submit-cookie-pattern)** - **If an API-driven site can't use `
` tags, consider [using custom request headers](#employing-custom-request-headers-for-ajaxapi)** @@ -25,13 +26,16 @@ In short, the following principles should be followed to defend against CSRF: - **Do not use GET requests for state changing operations.** - **If for any reason you do it, protect those resources against CSRF** -## Token-Based Mitigation +### Use Built-In Or Existing CSRF Implementations for CSRF Protection -The [synchronizer token pattern](#synchronizer-token-pattern) is one of the most popular and recommended methods to mitigate CSRF. +Before building a custom token or Fetch-Metadata implementation, check whether your framework or platform already provides CSRF protection you can use. Built-in defenses are generally preferable because they’re maintained by the framework authors and reduce the risk of subtle implementation mistakes. For example: -### Use Built-In Or Existing CSRF Implementations for CSRF Protection +- .NET can use [built-in protection](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.1) to add tokens to CSRF vulnerable resources. If you choose to use this protection, .NET makes you responsible for proper configuration (such as key management and token management). +- Starting from [1.25](https://pkg.go.dev/net/http@go1.25rc2), Go developers can rely on the built-in [CrossOriginProtection](https://pkg.go.dev/net/http@go1.25rc2#CrossOriginProtection) type. It implements a Fetch-Metadata-based CSRF defense (including validation of Sec-Fetch-Site and related headers) directly in the standard library. -Since synchronizer token defenses are built into many frameworks, find out if your framework has CSRF protection available by default before you build a custom token generating system. For example, .NET can use [built-in protection](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.1) to add tokens to CSRF vulnerable resources. If you choose to use this protection, .NET makes you responsible for proper configuration (such as key management and token management). +## Token-Based Mitigation + +The [synchronizer token pattern](#synchronizer-token-pattern) is one of the most popular and recommended methods to mitigate CSRF. ### Synchronizer Token Pattern @@ -151,6 +155,95 @@ Since an attacker is unable to access the cookie value during a cross-site reque Though the Naive Double-Submit Cookie method is simple and scalable, it remains vulnerable to cookie injection attacks, especially when attackers control subdomains or network environments allowing them to plant or overwrite cookies. For instance, an attacker-controlled subdomain (e.g., via DNS takeover) could inject a matching cookie and thus forge a valid request token. [This resource](https://owasp.org/www-chapter-london/assets/slides/David_Johansson-Double_Defeat_of_Double-Submit_Cookie.pdf) details these vulnerabilities. Therefore, always prefer the _Signed Double-Submit Cookie_ pattern with session-bound HMAC tokens to mitigate these threats. +## Fetch Metadata headers + +Fetch Metadata request headers provide extra context about how an HTTP request was made, and how the resource will be used, enabling servers to reject suspicious cross-site requests. Servers can use these headers — most importantly `Sec-Fetch-Site` — as a lightweight and reliable method to block obvious cross-site requests. See the [Fetch Metadata specification](https://www.w3.org/TR/fetch-metadata/) for details. + +Because some legacy browsers may not send `Sec-Fetch-*` headers, a fallback to [standard origin verification](#using-standard-headers-to-verify-origin) using the `Origin` and `Referer` headers **is a mandatory requirement** for any Fetch Metadata implementation. `Sec-Fetch-*` [is supported](https://caniuse.com/?search=sec-fetch-site) in all browsers since March 2023 (with the exception of IE as it no longer receives updates and does not support `Sec-Fetch-*` headers). + +The Fetch Metadata request headers are: + +- Sec-Fetch-Site — the primary signal for CSRF protection. It indicates relationship between request initiator’s origin and it's target's origin: `same-origin`, `same-site`, `cross-site`, or `none`. +- Sec-Fetch-Mode, Sec-Fetch-Dest, Sec-Fetch-User — additional headers that provide context about the request (such as the request mode, destination type, or whether it was triggered by a user navigation). More details are available in the [MDN documentation](https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header). + +If any of headers above contain values not listed in the specification, in order to support forward-compatibility, servers should ignore those headers. + +### Ease of use + +Unlike [synchronizer tokens](#synchronizer-token-pattern) or [double-submit patterns](#alternative-using-a-double-submit-cookie-pattern) — which require additional client/server coordination and are easy to misimplement — Fetch Metadata checks are straightforward to implement correctly. They typically require only a small amount of server-side logic (inspect Sec-Fetch-Site, optionally refine with Sec-Fetch-Mode/Sec-Fetch-Dest) and no client changes. That simplicity reduces complexity, making the approach attractive for many applications. + +### Browser compatibility + +Fetch Metadata request headers are supported in most modern browsers on both desktop and mobile (Chrome, Edge, Firefox, Safari 16.4+, and even in webviews on both iOS and Android), with [over 98% global coverage](https://caniuse.com/mdn-http_headers_sec-fetch-site). For compatibility detail, see the [browser support table](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site#browser_compatibility). + +For the rare cases of outdated or embedded browsers that lack `Sec-Fetch-*` support, a fallback to [standard origin verification](#using-standard-headers-to-verify-origin) should provide the required coverage. If this is acceptable for your project, consider prompting users to update their browsers, as they are running on outdated and potentially insecure versions. + +### How to treat Fetch Metadata headers on the server-side + +`Sec-Fetch-Site` is the most useful Fetch Metadata header for blocking CSRF-like cross-origin requests and should be the primary signal in a Fetch-Metadata-based policy. Use other Fetch Metadata headers (`Sec-Fetch-Mode`, `Sec-Fetch-Dest`, `Sec-Fetch-User`) to further refine or tailor policies to your application (for example, allowing navigate mode top-level requests or permitting specific Dest values for resource endpoints). +**Policy (high level)** + +1. If `Sec-Fetch-Site` is present: + 1.1. Treat cross-site as untrusted for state-changing actions. By default, reject non-safe methods (POST / PUT / PATCH / DELETE) when `Sec-Fetch-Site: cross-site`. + + ```JavaScript + const SAFE = new Set(['GET','HEAD','OPTIONS']); + const site = req.get('Sec-Fetch-Site'); // e.g. 'cross-site','same-site','same-origin','none' + + if (site === 'cross-site' && !SAFE.has(req.method)) { + return false; // forbid this request + } + ``` + + 1.2. Allow `same-origin`. Treat `same-site` as allowed only if your threat model trusts sibling subdomains; otherwise handle `same-site` conservatively (for example, require additional validation). + + ```JavaScript + const trustSameSite = false; // set true only if you trust sibling subdomains + + if (site === 'same-origin') { + return true; + } else if (site === 'same-site') { + // handle same-site separately so the subcondition is clearly scoped to same-site + if (!trustSameSite && !SAFE.has(req.method)) { + return false; // treat same-site as untrusted for state-changing methods + } + return true; + } + ``` + + 1.3. Allow none for user-driven top-level navigations (bookmarks, typed URLs, explicit form submits) where appropriate. + +2. If `Sec-Fetch-*` headers are absent: choose a fallback based on risk and compatibility requirements: + 2.1. Fail-safe (recommended for sensitive endpoints): treat absence as unknown and block the request. + 2.2. Fail-open (compatibility-first): fallback to other security measure ([standard origin verification](#using-standard-headers-to-verify-origin), CSRF tokens, and/or require additional validation). + +3. Additional options + 3.1 To ensure that your site can still be linked from other sites, you have to allow simple (HTTP GET) top-level navigation. + + ```JavaScript + if (req.get('Sec-Fetch-Mode') === 'navigate' && + req.method === 'GET' && + req.get('Sec-Fetch-Dest') !== 'object' && + req.get('Sec-Fetch-Dest') !== 'embed') { + return true; // Allow this request + } + ``` + + 3.2 Whitelist explicit cross-origin flows. If certain endpoints intentionally accept cross-origin requests (CORS JSON APIs, third-party integrations, webhooks), explicitly exempt those endpoints from the global Sec-Fetch deny policy and secure them with proper CORS configuration, authentication, and logging. + +### Requirements + +- Your application must be served over trustworthy URLs. Fetch Metadata request headers are only sent to [potentially trustworthy URLs](https://www.w3.org/TR/secure-contexts/#is-url-trustworthy). In practice, this includes `https`, `wss`, `file`, and `localhost` (including `127.0.0.0/8` and `::1/128`). See the [W3C Secure Contexts spec](https://www.w3.org/TR/secure-contexts/#is-origin-trustworthy) for full details. +- HTTPS must be enforced across the entire application. This ensures consistent inclusion of Fetch Metadata headers. Enabling [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security) helps achieve this by automatically upgrading all HTTP requests to HTTPS. +- [Safe HTTP methods](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP) must not be used for state-changing requests. + +### Rollout & testing recommendations + +- Include an appropriate `Vary` header, in order to ensure that caches handle the response appropriately. For example, `Vary: Accept-Encoding, Sec-Fetch-Site`. See more [Fetch Metadata specification](https://w3c.github.io/webappsec-fetch-metadata/#vary). +- Start in “log only” mode. Record requests that would be blocked and review for false positives before enforcing. This is the safest way to discover legitimate flows that need whitelisting. +- Monitor UA coverage. Track which user agents include `Sec-Fetch-*` and which don’t; ensure your fallback logic covers missing-header cases. Use metrics to decide when to enforce stricter policies. +- Document exceptions. Keep an explicit list of endpoints whitelisted for cross-origin access. + ## Disallowing simple requests When a `` tag is used to submit data, it sends a ["simple" request](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) that browsers do not designate as "to be preflighted". These "simple" requests introduce risk of CSRF because browsers permit them to be sent to any origin. If your application uses `` tags to submit data anywhere in your client, you will still need to protect them with alternate approaches described in this document such as tokens. @@ -368,89 +461,6 @@ Cookie prefixes [are supported by all major browsers](https://developer.mozilla. See the [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Directives) and [IETF Draft](https://tools.ietf.org/html/draft-west-cookie-prefixes-05) for further information about cookie prefixes. -### Use Fetch Metadata headers to verify nature of the request - -Fetch Metadata request headers provide extra context about how an HTTP request was made, and how the resource will be used, enabling servers to reject suspicious cross-site requests. Servers can use these headers — most importantly `Sec-Fetch-Site` — as a lightweight method to block obvious cross-site requests. See the [Fetch Metadata specification](https://www.w3.org/TR/fetch-metadata/) for details. - -It is important to note that Fetch Metadata headers should be implemented as an additional layer defense in depth concept. This attribute should not replace a CSRF tokens (or equivalent framework protections). - -The Fetch Metadata request headers are: - -- Sec-Fetch-Site — indicates relationship between request initiator’s origin and it's target's origin: `same-origin`, `same-site`, `cross-site`, or `none`. -- Sec-Fetch-Mode — indicates the request's [mode](https://fetch.spec.whatwg.org/#concept-request-mode)(e.g., `navigate`, `no-cors`, `cors`, `same-origin`, and `websocket`), which allows to distinguish between requests originating from a user navigating between HTML pages, and requests to load images and other resources. -- Sec-Fetch-Dest — indicates the [destination](https://fetch.spec.whatwg.org/#concept-request-destination) for the requested resource (e.g., `document`, `image`, `script`, etc.). -- Sec-Fetch-User — present only for navigation requests initiated by user. When sent value is `?1`, meaning `true`. - -If any of headers above contain values not listed in the specification, in order to support forward-compatibility, servers should ignore those headers. - -Browser compatability: Fetch Metadata request headers are supported in most modern browsers on both desktop and mobile (Chrome, Edge, Firefox, Safari 16.4+, and even in webviews on both iOS and Android). For compatibility detail, see the [browser support table](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site#browser_compatibility). - -#### How to treat Fetch Metadata headers on the server-side - -Sec-Fetch-Site is the most useful Fetch Metadata header for blocking CSRF-like cross-origin requests and should be the primary signal in a Fetch-Metadata-based policy. Use other Fetch Metadata headers (Sec-Fetch-Mode, Sec-Fetch-Dest, Sec-Fetch-User) to further refine or tailor policies to your application (for example, allowing navigate mode top-level requests or permitting specific Dest values for resource endpoints). -**Policy (high level)** - -1. If Sec-Fetch-Site is present: - 1.1. Treat cross-site as untrusted for state-changing actions. By default, reject non-safe methods (POST / PUT / PATCH / DELETE) when `Sec-Fetch-Site: cross-site`. - - ```JavaScript - const SAFE = new Set(['GET','HEAD','OPTIONS']); - const site = req.get('Sec-Fetch-Site'); // e.g. 'cross-site','same-site','same-origin','none' - - if (site === 'cross-site' && !SAFE.has(req.method)) { - return false; // forbid this request - } - ``` - - 1.2. Allow `same-origin`. Treat `same-site` as allowed only if your threat model trusts sibling subdomains; otherwise handle `same-site` conservatively (for example, require additional validation). - - ```JavaScript - const trustSameSite = false; // set true only if you trust sibling subdomains - - if (site === 'same-origin') { - return true; - } else if (site === 'same-site') { - // handle same-site separately so the subcondition is clearly scoped to same-site - if (!trustSameSite && !SAFE.has(req.method)) { - return false; // treat same-site as untrusted for state-changing methods - } - return true; - } - ``` - - 1.3. Allow none for user-driven top-level navigations (bookmarks, typed URLs, explicit form submits) where appropriate. - -2. If Sec-Fetch-* headers are absent: choose a fallback based on risk and compatibility requirements: - 2.1. Fail-safe (recommended for sensitive endpoints): treat absence as unknown and block the request. - 2.2. Fail-open (compatibility-first): fallback to other security measure (CSRF tokens, validate Origin/Referer, and/or require additional validation). - -3. Additionall options - 3.1 To ensure that your site can still be linked from other sites, you have to allow simple (HTTP GET) top-level navigation. - - ```JavaScript - if (req.get('Sec-Fetch-Mode') === 'navigate' && - req.method === 'GET' && - req.get('Sec-Fetch-Dest') !== 'object' && - req.get('Sec-Fetch-Dest') !== 'embed') { - return true; // Allow this request - } - ``` - - 3.2 Whitelist explicit cross-origin flows. If certain endpoints intentionally accept cross-origin requests (CORS JSON APIs, third-party integrations, webhooks), explicitly exempt those endpoints from the global Sec-Fetch deny policy and secure them with proper CORS configuration, authentication, and logging. - -#### Limitations and gotchas - -- Not universal. Some older browsers, webviews, bots, and non-browser HTTP clients do not send Sec-Fetch-*. Do not assume presence on every request — implement fallbacks. -- May break legitimate cross-origin integrations. A global Sec-Fetch policy can unintentionally block legitimate CORS or third-party flows; plan explicit whitelisting. -- One limitation is that Fetch Metadata request headers are only sent to [potentially trustworthy URLs](https://www.w3.org/TR/secure-contexts/#is-url-trustworthy). This means the headers will generally be present for requests to origins whose scheme is `https`, `wss`, or `file`, and for `localhost` (hosts in the `127.0.0.0/8` or `::1/128` ranges). For the full rules and additional edge cases (the algorithm the user agent uses to decide trustworthiness), see the [W3C Secure Contexts spec](https://www.w3.org/TR/secure-contexts/#is-origin-trustworthy). - -#### Rollout & testing recommendations - -- Include an appropriate Vary header [RFC9110], in order to ensure that caches handle the response appropriately. For example, `Vary: Accept-Encoding, Sec-Fetch-Site`. See more [Fetch Metadata specification](https://w3c.github.io/webappsec-fetch-metadata/#vary). -- Start in “log only” mode. Record requests that would be blocked and review for false positives before enforcing. This is the safest way to discover legitimate flows that need whitelisting. -- Monitor UA coverage. Track which user agents include Sec-Fetch-* and which don’t; ensure your fallback logic covers missing-header cases. Use metrics to decide when to enforce stricter policies. -- Document exceptions. Keep an explicit list of endpoints whitelisted for cross-origin access. - ### User Interaction-Based CSRF Defense While all the techniques referenced here do not require any user interaction, sometimes it's easier or more appropriate to involve the user in the transaction to prevent unauthorized operations (forged via CSRF or otherwise). The following are some examples of techniques that can act as strong CSRF defense when implemented correctly.