Thilan Dissanayaka Application Security May 27

CSRF - Cross Site Request Forgery

Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform. It occurs when a malicious website, email, or program causes a user's web browser to perform an unwanted action on a trusted site where the user is currently authenticated.

CSRF attacks specifically target state-changing requests, not theft of data, since the attacker cannot see the response to the forged request. With a little help from social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing.

How CSRF Attacks Work

The fundamental principle behind CSRF is that web browsers automatically include credentials (cookies, authentication headers, etc.) with requests to a domain. When a user is authenticated to a website, their browser stores session cookies that are sent with every subsequent request to that domain.

Here's the typical CSRF attack flow:

  • User Authentication: The victim logs into a legitimate website (e.g., their banking site)
  • Session Establishment: The website sets a session cookie in the user's browser
  • Malicious Request: While still logged in, the user visits a malicious website or clicks a malicious link
  • Forged Request: The malicious site triggers a request to the legitimate website
  • Automatic Authentication: The browser automatically includes the session cookie with the request
  • Unauthorized Action: The legitimate website processes the request as if it came from the authenticated user

PHP Web Application Example

Let's examine a vulnerable PHP application and then see how to protect it.

Vulnerable Application

Consider a simple banking application with the following structure:

index.php (Login page)

<?php
session_start();

if ($_POST['username'] && $_POST['password']) {
    // Simple authentication (in real apps, use proper password hashing)
    if ($_POST['username'] === 'user' && $_POST['password'] === 'password') {
        $_SESSION['authenticated'] = true;
        $_SESSION['username'] = $_POST['username'];
        $_SESSION['balance'] = 1000; // Initial balance
        header('Location: dashboard.php');
        exit;
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Bank Login</title>
</head>
<body>
    <h2>Bank Login</h2>
    <form method="POST">
        <input type="text" name="username" placeholder="Username" required><br><br>
        <input type="password" name="password" placeholder="Password" required><br><br>
        <button type="submit">Login</button>
    </form>
</body>
</html>

dashboard.php (User dashboard)

<?php
session_start();

if (!$_SESSION['authenticated']) {
    header('Location: index.php');
    exit;
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Bank Dashboard</title>
</head>
<body>
    <h2>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</h2>
    <p>Your balance: $<?php echo $_SESSION['balance']; ?></p>

    <h3>Transfer Money</h3>
    <form action="transfer.php" method="POST">
        <input type="text" name="recipient" placeholder="Recipient" required><br><br>
        <input type="number" name="amount" placeholder="Amount" required><br><br>
        <button type="submit">Transfer</button>
    </form>

    <br><a href="logout.php">Logout</a>
</body>
</html>

transfer.php (Vulnerable transfer handler)

<?php
session_start();

if (!$_SESSION['authenticated']) {
    header('Location: index.php');
    exit;
}

if ($_POST['recipient'] && $_POST['amount']) {
    $recipient = $_POST['recipient'];
    $amount = (int)$_POST['amount'];

    if ($amount > 0 && $amount <= $_SESSION['balance']) {
        $_SESSION['balance'] -= $amount;

        // In a real application, you would transfer to the recipient
        echo "<h2>Transfer Successful!</h2>";
        echo "<p>Transferred $" . $amount . " to " . htmlspecialchars($recipient) . "</p>";
        echo "<p>Remaining balance: $" . $_SESSION['balance'] . "</p>";
        echo '<a href="dashboard.php">Back to Dashboard</a>';
    } else {
        echo "<h2>Transfer Failed!</h2>";
        echo "<p>Insufficient funds or invalid amount.</p>";
        echo '<a href="dashboard.php">Back to Dashboard</a>';
    }
} else {
    header('Location: dashboard.php');
}
?>

The CSRF Attack

Now, let's create a malicious website that exploits this vulnerability:

malicious-site.html

<!DOCTYPE html>
<html>
<head>
    <title>Innocent Looking Website</title>
</head>
<body>
    <!-- Hidden malicious form -->
    <form id="maliciousForm" action="http://vulnerable-bank.com/transfer.php" method="POST" style="display: none;">
        <input type="hidden" name="recipient" value="[email protected]">
        <input type="hidden" name="amount" value="500">
    </form>

    <script>
        document.getElementById('maliciousForm').submit();
    </script>
</body>
</html>

When a logged-in user visits this malicious site and clicks the "More Cats!" button, their browser will submit a POST request to the banking site, transferring $500 to the attacker's account. Since the user is still logged in, their session cookie will be automatically included, making the request appear legitimate.

CSRF Protection Methods

1. CSRF Tokens (Synchronizer Token Pattern)

The most common and effective defense against CSRF is using CSRF tokens. Here's how to implement it:

Updated dashboard.php with CSRF token

<?php
session_start();

if (!$_SESSION['authenticated']) {
    header('Location: index.php');
    exit;
}

// Generate CSRF token
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Bank Dashboard</title>
</head>
<body>
    <h2>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</h2>
    <p>Your balance: $<?php echo $_SESSION['balance']; ?></p>

    <h3>Transfer Money</h3>
    <form action="transfer.php" method="POST">
        <!-- CSRF Token -->
        <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">

        <input type="text" name="recipient" placeholder="Recipient" required><br><br>
        <input type="number" name="amount" placeholder="Amount" required><br><br>
        <button type="submit">Transfer</button>
    </form>

    <br><a href="logout.php">Logout</a>
</body>
</html>

Updated transfer.php with CSRF protection

<?php
session_start();

if (!$_SESSION['authenticated']) {
    header('Location: index.php');
    exit;
}

// CSRF Token validation
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    die('<h2>CSRF Attack Detected!</h2><p>Invalid or missing CSRF token.</p><a href="dashboard.php">Back to Dashboard</a>');
}

if ($_POST['recipient'] && $_POST['amount']) {
    $recipient = $_POST['recipient'];
    $amount = (int)$_POST['amount'];

    if ($amount > 0 && $amount <= $_SESSION['balance']) {
        $_SESSION['balance'] -= $amount;

        // Regenerate CSRF token after successful action
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));

        echo "<h2>Transfer Successful!</h2>";
        echo "<p>Transferred $" . $amount . " to " . htmlspecialchars($recipient) . "</p>";
        echo "<p>Remaining balance: $" . $_SESSION['balance'] . "</p>";
        echo '<a href="dashboard.php">Back to Dashboard</a>';
    } else {
        echo "<h2>Transfer Failed!</h2>";
        echo "<p>Insufficient funds or invalid amount.</p>";
        echo '<a href="dashboard.php">Back to Dashboard</a>';
    }
} else {
    header('Location: dashboard.php');
}
?>

2. SameSite Cookie Attribute

Modern browsers support the SameSite cookie attribute, which helps prevent CSRF attacks:

// Set session cookie with SameSite attribute
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'your-domain.com',
    'secure' => true,  // Only send over HTTPS
    'httponly' => true, // Prevent XSS access
    'samesite' => 'Strict' // or 'Lax'
]);
session_start();

3. Double Submit Cookie Pattern

Another approach is the double submit cookie pattern:

// Set CSRF cookie
if (!isset($_COOKIE['csrf_token'])) {
    $csrf_token = bin2hex(random_bytes(32));
    setcookie('csrf_token', $csrf_token, [
        'expires' => time() + 3600,
        'path' => '/',
        'secure' => true,
        'httponly' => false, // JavaScript needs access
        'samesite' => 'Strict'
    ]);
} else {
    $csrf_token = $_COOKIE['csrf_token'];
}

// In forms, include the token as a hidden field
// In AJAX requests, read from cookie and include in headers

4. Custom Request Headers

For AJAX requests, require custom headers:

// JavaScript AJAX request with custom header
fetch('/transfer.php', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Protection': '1'
    },
    body: JSON.stringify({
        recipient: '[email protected]',
        amount: 100
    })
});
// PHP validation
if (!isset($_SERVER['HTTP_X_CSRF_PROTECTION'])) {
    die('CSRF protection header missing');
}

Best Practices for CSRF Prevention

1. Always Validate CSRF Tokens

  • Generate cryptographically strong tokens
  • Validate tokens on every state-changing request
  • Regenerate tokens after successful actions

2. Use Proper HTTP Methods

  • Use POST, PUT, DELETE for state-changing operations
  • Never use GET requests for actions that modify data

3. Implement Proper Session Management

// Secure session configuration
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_strict_mode', 1);

4. Content-Type Validation

// Only accept JSON for API endpoints
if ($_SERVER['CONTENT_TYPE'] !== 'application/json') {
    http_response_code(400);
    die('Invalid content type');
}

5. Origin and Referer Checking

// Check Origin header
$allowed_origins = ['https://your-domain.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';

if (!in_array($origin, $allowed_origins)) {
    http_response_code(403);
    die('Forbidden origin');
}

Testing for CSRF Vulnerabilities

Manual Testing

  1. Log into the application
  2. Capture a state-changing request
  3. Create a malicious HTML page that replicates the request
  4. Visit the malicious page while logged in
  5. Check if the action was executed

Automated Testing Tools

  • OWASP ZAP
  • Burp Suite
  • CSRFTester

Real-World Impact

CSRF vulnerabilities can lead to:

  • Unauthorized financial transactions
  • Account takeovers
  • Data modification or deletion
  • Privilege escalation
  • Social engineering attacks

Notable real-world examples include attacks on major platforms like Gmail, Netflix, and various banking applications.

ALSO READ
Observer Pattern explained simply
Apr 26 Software Architecture

When one object needs to notify many other objects about changes in its state **automatically**, the **Observer Pattern** steps in. ## What is the Observer Pattern? - Defines a....

Common Web Application Attacks
May 17 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these....

ACID Properties in Databases: The Key to Reliable Transactions
Apr 25 Database Systems

When working with databases, one thing is absolutely critical: keeping your data safe, consistent, and reliable. That's where ACID properties come in — a set of principles that ensure every....

AWS - Interview preparation guide
May 08 Interview Guides

## What is Amazon EC2 and what are its features? Amazon EC2 (Elastic Compute Cloud) is a web service that provides resizable compute capacity in the cloud. It allows you to launch and manage....

Docker - Interview preparation guide
May 08 Interview Guides

## What is Docker and why is it used? Docker is a platform for developing, shipping, and running applications in containers. Containers package an application with its dependencies, ensuring....

Abstract Factory Pattern explained simply
Apr 26 Software Architecture

When you want to create **families of related objects** without specifying their concrete classes, the **Abstract Factory Pattern** is your best friend. --- ## What is the Abstract Factory....