Cross-Site Request Forgery CSRF is a vulnerability that allows an attacker to trick a logged-in user into performing unwanted actions on a website without…
Cross-Site Request Forgery (CSRF) is a vulnerability that allows an attacker to trick a logged-in user into performing unwanted actions on a website without their knowledge or consent. Because the user's browser automatically includes authentication cookies with requests to that site, the attacker can forge requests that appear to come from the legitimate user—such as changing a password, transferring funds, or deleting data—without the user realizing what happened.
02How It Happens
CSRF exploits the way browsers handle authentication. When you log into a website, your browser stores a session cookie. Every subsequent request to that site automatically includes that cookie, proving you're authenticated. An attacker can create a malicious webpage or email that, when visited by a logged-in user, silently triggers an action on the target site—such as a form submission or API call—using only HTML, JavaScript, or an image tag. Since the request originates from the user's browser and includes their valid session cookie, the server cannot distinguish it from a legitimate user action. The vulnerability exists because the application does not verify that the request was *intentionally* initiated by the user, only that it came from an authenticated session.
03Real-World Impact
CSRF can lead to unauthorized account modifications, unauthorized financial transactions, privilege escalation, or data deletion. In a banking context, an attacker could transfer funds from a victim's account. On social media, an attacker could post content, change account settings, or delete data on behalf of the victim. On administrative interfaces, an attacker could create new user accounts or modify permissions. Because the victim's browser is the one making the request, the attack is difficult for users to detect and leaves audit trails that appear to show the victim as the actor.
04Vulnerable & Fixed Patterns
Vulnerable pattern
from flask import Flask, request, session, redirect
app = Flask(__name__)
app.secret_key = 'secret'
@app.route('/change_email', methods=['POST'])
def change_email():
if 'user_id' not in session:
return 'Not authenticated', 401
new_email = request.form.get('email')
# Update email in database without verifying request origin
db.update_user_email(session['user_id'], new_email)
return 'Email changed successfully'
Why it's vulnerable: The endpoint accepts POST requests from any origin and does not verify that the request came from the application itself. An attacker can embed a form on an external site that, when submitted by a logged-in user, will change their email without their knowledge.
Fixed pattern
from flask import Flask, request, session, redirect
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.secret_key = 'secret'
csrf = CSRFProtect(app)
@app.route('/change_email', methods=['POST'])
@csrf.protect
def change_email():
if 'user_id' not in session:
return 'Not authenticated', 401
new_email = request.form.get('email')
db.update_user_email(session['user_id'], new_email)
return 'Email changed successfully'
Vulnerable pattern
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
die('Not authenticated');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new_email = $_POST['email'];
// Update email without verifying request origin
$db->query("UPDATE users SET email = ? WHERE id = ?",
[$new_email, $_SESSION['user_id']]);
echo 'Email changed successfully';
}
?>
Why it's vulnerable: The script processes POST requests without checking for a CSRF token. An attacker can create a hidden form on another site that submits to this endpoint, and a logged-in user's browser will automatically include their session cookie.
Fixed pattern
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
die('Not authenticated');
}
// Generate token on GET (form display)
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
echo '<form method="POST">
<input type="hidden" name="csrf_token" value="' .
htmlspecialchars($_SESSION['csrf_token']) . '">
<input type="email" name="email">
<button type="submit">Change Email</button>
</form>';
}
// Verify token on POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($_POST['csrf_token']) ||
$_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}
$new_email = $_POST['email'];
$db->query("UPDATE users SET email = ? WHERE id = ?",
[$new_email, $_SESSION['user_id']]);
echo 'Email changed successfully';
}
?>
05Prevention Checklist
Implement CSRF tokens: Generate a unique, unpredictable token for each user session and require it in all state-changing requests (POST, PUT, DELETE). Validate the token server-side before processing the request.
Use the SameSite cookie attribute: Set SameSite=Strict or SameSite=Lax on session cookies to prevent the browser from automatically including them in cross-site requests.
Verify the Origin and Referer headers: Check that requests originate from your own domain; reject requests with mismatched or missing headers (though note these can be spoofed in some contexts, so use them as a secondary defense).
Avoid GET for state-changing operations: Reserve GET requests for read-only operations; use POST, PUT, or DELETE for modifications, making it harder for attackers to trigger actions via simple image tags or links.
Use framework CSRF protection: Leverage built-in CSRF middleware in your framework (Django, Flask-WTF, Laravel, WordPress, etc.) rather than implementing it manually.
Educate users: Advise users to log out after sensitive sessions and to be cautious about clicking links in emails or visiting untrusted sites while logged into banking or email accounts.
06Signs You May Already Be Affected
Monitor your application logs for unexpected state-changing requests (password changes, email updates, fund transfers) that lack a corresponding user action in your UI logs or that originate from unusual referrer headers. Check for unusual account modifications or administrative actions that users deny initiating. If you find POST requests succeeding without a CSRF token validation step in your code, you are vulnerable.