Cross-Site Request Forgery (CSRF, pronounced “sea-surf”) is a sneaky and dangerous web vulnerability. Classified as CWE-352 by MITRE, it allows attackers to trick authenticated users into unknowingly submitting malicious requests to a web application. Imagine being logged into your bank account and clicking a seemingly harmless link that secretly instructs your browser to transfer funds to an attacker – that’s CSRF in action.
Why CSRF is Dangerous (CWE-352):
- Exploits Trust: The target site trusts the user’s browser because it sends valid session cookies automatically with every request.
- User Unawareness: The victim executes the malicious action entirely unknowingly, often with a single click.
- Impact: Can lead to account takeover (changing email/password), unauthorized fund transfers, data theft, or altering application state.
Core Defense: Breaking the Forgery with the Synchronizer Token Pattern
The most robust and widely recommended defense is the Synchronizer Token Pattern. Here’s how it works:
- Token Generation: When a user logs in or loads a form that performs a sensitive state-changing action (POST, PUT, DELETE, etc.), the server generates a unique, unpredictable, cryptographically strong token. This token is associated with the user’s session.
- Token Transmission: The server includes this token in the HTML form (as a hidden field) or attaches it to the response headers (for AJAX/fetch requests).
- Client Submission: When the user submits the form or makes the sensitive request, the browser automatically includes this token along with the request data and session cookies.
- Server Validation: The server receiving the request must check that the submitted token exists and matches the token stored for the user’s session.
- Action Execution: The sensitive action is only executed if the token validation is successful.
Implementing the Synchronizer Token Pattern Effectively:
- Unpredictability: Use a cryptographically secure random number generator (CSPRNG) to create tokens (e.g.,
crypto.randomBytes(32).toString('hex')
in Node.js). Avoid predictable values like timestamps or user IDs alone. - Session Association: Store the token securely on the server, tightly bound to the user’s session. Do not store it in cookies accessible to JavaScript.
- Per-Session or Per-Request? Per-request tokens offer maximum security (invalidated after one use) but can be complex. Per-session tokens are simpler but slightly less secure if leaked/sniffed. Per-form is a common middle ground.
- Include in ALL State-Changing Requests: Apply tokens to forms and API endpoints that change data (POST, PUT, DELETE, PATCH). GET requests generally should not change state, making them less critical (but see below).
- Secure Transmission: Transmit tokens over HTTPS only. Consider setting the
__Host-
prefix on cookies if used (requires HTTPS and strict path).
Complementary Defenses:
SameSite Cookies:
- Set the
SameSite
attribute for session cookies.SameSite=Lax
(default in modern browsers) prevents cookies from being sent on cross-site POST requests (common CSRF vector) but allows GET links.SameSite=Strict
offers stronger protection but can break user experience when following legitimate links from other sites. SameSite=None; Secure
is only needed if your site must function within cross-site iframes and requires session cookies there (use with extreme caution and CSRF tokens!).
- Set the
Checking Standard Headers (Origin/Referer):
- Verify the
Origin
orReferer
header on incoming requests. Legitimate requests from your own site will have a matching origin (e.g.,https://yourdomain.com
). Malicious requests from other sites will have a different origin or lack these headers. - Important: This should be a secondary defense. Headers can sometimes be missing (privacy settings, proxies) or spoofable in specific older browser configurations. Never rely solely on this.
- Verify the
Framework Solutions:
- Leverage Your Framework: Most modern web frameworks (Django CSRF middleware, Spring Security CSRF, Laravel CSRF, Express
csurf
/built-in middleware) have built-in, battle-tested CSRF protection implementations. USE THEM! They handle token generation, embedding, and validation automatically.
- Leverage Your Framework: Most modern web frameworks (Django CSRF middleware, Spring Security CSRF, Laravel CSRF, Express
What NOT to Rely On:
- GET Requests for State Changes: GET requests should be idempotent (safe to repeat) and only retrieve data. Never use GET requests for actions that change state (deleting, purchasing, updating). Attackers can easily forge GET requests using
<img>
tags. - Secret Cookies: Session cookies are sent automatically by the browser with every request to the domain, including forged ones. Adding another secret cookie suffers the same problem – it will be included in the forged request too.
- Re-Authentication (Alone): While asking for a password for critical actions (like changing an email) adds a layer of security (defense-in-depth), it harms UX and shouldn’t replace CSRF tokens. Sophisticated attacks might bypass it.
Verify Your Protection: Online Scanning
Don’t just assume it works! Test your implementation:
- ScyScan (Free Web Security Scanner): This powerful, free, online tool includes an active scanner that can detect CSRF vulnerabilities (CWE-352).
Conclusion
CSRF (CWE-352) remains a significant threat by exploiting the inherent trust between browsers and websites. The Synchronizer Token Pattern, implemented correctly (preferably using your framework’s built-in tools), is the cornerstone of defense. Strengthen this with SameSite cookies and consider Origin/Referer checks as secondary measures. Avoid unsafe practices like using GET for state changes. Most importantly, actively test your defenses using tools like OWASP ZAP to ensure you’ve closed the door on this silent attacker. Security is layered – implement these protections diligently to keep your users and their data safe.
References & Further Reading:
- CWE-352: Cross-Site Request Forgery (CSRF)
- ScyScan (Scanner): https://www.scyscan.com/
- MDN Web Docs - SameSite Cookies: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite