Cross-Site Request Forgery (CSRF) is an attack that forces authenticated users to execute unwanted actions on a web application in which they're currently authenticated. The attack works by tricking the victim into clicking a link or loading a page that contains a malicious request.
Account Number: 123456789
| Date | Description | Amount |
|---|---|---|
| 2025-03-20 | Salary | $3,000.00 |
| 2025-03-15 | Grocery Store | $150.00 |
| 2025-03-10 | Gas Station | $45.00 |
This is a malicious website that contains a hidden form targeting the banking application.
Click the button below to claim your prize now!
When the victim clicks the button above, it submits a hidden form to the bank application that transfers $1,000 to the attacker's account. Since the victim is already logged into the bank, the request includes their session cookie, making it appear legitimate.
<form id="csrf-form" action="http://example.com/bank.php" method="post">
<input type="hidden" name="recipient" value="hacker">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="transfer" value="1">
</form>
Account Number: 123456789
| Date | Description | Amount |
|---|---|---|
| 2025-03-20 | Salary | $3,000.00 |
| 2025-03-15 | Grocery Store | $150.00 |
| 2025-03-10 | Gas Station | $45.00 |
<img src="https://bank.com/transfer?to=attacker&amount=1000">The most common defense is to include a unique, unpredictable token with each request that requires a state change:
// Generate a token
$csrf_token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $csrf_token;
// Include in HTML form
echo '<input type="hidden" name="csrf_token" value="' . $csrf_token . '">';
// Verify on submission
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
// Invalid token, reject the request
}
Modern browsers support the SameSite attribute which controls when cookies are sent with cross-site requests:
// Strict: Cookies are only sent for same-site requests
setcookie('session', $value, ['samesite' => 'Strict']);
// Lax: Cookies are sent for same-site requests and top-level navigations
setcookie('session', $value, ['samesite' => 'Lax']); // Default in modern browsers
Require a custom header that browsers won't allow in cross-site requests due to CORS restrictions:
// JavaScript adds a custom header
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/transfer');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// Server verifies the header exists
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH']) ||
$_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
// Reject the request
}
Set a random cookie value and require the same value as a request parameter:
// Set a cookie with a random value
setcookie('csrf', $randomValue);
// Include the same value in the form
echo '<input type="hidden" name="csrf" value="' . $randomValue . '">';
// Verify values match
if ($_COOKIE['csrf'] !== $_POST['csrf']) {
// Reject the request
}
Require the user to confirm their identity before critical actions:
// Before processing a sensitive action
if (!isset($_POST['password'])) {
// Show confirmation form
} else {
// Verify password matches the user's password
if (password_verify($_POST['password'], $user['password_hash'])) {
// Process the action
}
}