← Back to All Vulnerabilities

Cross-Site Request Forgery (CSRF) Vulnerability

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.

CSRF Demonstration

Account Summary

Account Number: 123456789

Transfer Money

Transaction History

Date Description Amount
2025-03-20 Salary $3,000.00
2025-03-15 Grocery Store $150.00
2025-03-10 Gas Station $45.00

Win a Free iPhone!

This is a malicious website that contains a hidden form targeting the banking application.

iPhone

Congratulations! You've Been Selected to Win a Free iPhone 15 Pro!

Click the button below to claim your prize now!

How this attack works:

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 Summary

Account Number: 123456789

Transfer Money (with CSRF Protection)

Transaction History

Date Description Amount
2025-03-20 Salary $3,000.00
2025-03-15 Grocery Store $150.00
2025-03-10 Gas Station $45.00

CSRF Protection Implemented:

  • A unique CSRF token is generated for each user session
  • The token is included as a hidden field in all forms
  • Server verifies that the submitted token matches the one in the session
  • Malicious sites cannot access or guess this token due to Same-Origin Policy

Anatomy of a CSRF Attack

Prerequisites for a Successful CSRF Attack:

  1. The victim is authenticated to the target site (has an active session)
  2. No unpredictable request parameters are required (no CSRF tokens)
  3. The attacker can determine all required request values needed to perform the action

Common CSRF Attack Vectors:

Impact of CSRF Attacks:

CSRF Protection Methods

1. CSRF Tokens

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
}
        

2. SameSite Cookie Attribute

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
        

3. Custom Request Headers

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
}
        

4. Double-Submit Cookie Pattern

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
}
        

5. Re-authentication for Sensitive Actions

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
    }
}