As we have a proper understanding of the CORS from the last chapter. We know that CORS prevents malicious JavaScript from reading responses, not from sending requests.
This means a CSRF attack can still succeed even on perfectly configured CORS setup.
Understanding the Core Problem
To understand CSRF deeply, you must first understand browser behavior.
Browsers automatically attach:
- Session cookies
- Authentication cookies
- Client certificates
- Cached credentials
to requests sent to matching domains.
This happens regardless of:
- Who initiated the request
- Which website triggered it
Example:
When you log into a website:
https://bank.comThe server gives your browser a session cookie:
Set-Cookie: session=abc123Your browser stores it automatically.
This is what keeps you logged in.
- Convenient for users.
- Dangerous for security.
Now the user visits:
https://evil.comThe attacker hosts:
<img src=https://bank.com/transfer?amount=5000&to=attacker>The browser automatically sends:
GET /transfer?amount=5000&to=attacker
Host: bank.com
Cookie: session_id=abc123The bank sees:
- Valid session cookie
- Authenticated user
and process the transfer.
The bank cannot distinguish:
Legitimate user action
vs
- Forged browser request
unless proper CSRF defenses exist.
Why CSRF Exists
CSRF exists because of three browser behaviors:
A. Automatic Credential Inclusion
Browsers automatically send cookies for matching domains.
This convenience feature enables:
- Persistent logins
- Sessions
- Seamless authentication
But it also creates the vulnerability.
B. Cross-Site Requests Are Allowed
Browsers allow websites to trigger requests to other sites using:
- Forms
- Images
- iframes
- Links
Example:
<form action="https://bank.com/transfer" method="POST">
<input type=hidden name=amount value="5000">
<input type=hidden name=to value="attacker">
</form>
<script>
document.forms[0].submit();
</script>The browser allows this request.
C. Servers Trust Cookies Too Much
Many applications assume:
If the request contains a valid session cookie, it must be legitimate.
This assumption is incorrect.
Cookies only prove:
- The browser is authenticated
They do NOT prove:
- The user intentionally initiated the request
This distinction is the heart of CSRF.
The Original Web Security Problem
Imagine:
- You are logged into your bank
Then you visit
https://evil.com
Now the malicious website tries to access your bank.
Two dangerous things could happen:
| Attack | Problem |
|---|---|
| Read your bank data | Privacy theft |
| Send actions as you | Unauthorized actions |
Browsers needed separate protections for these two problems.
Same Origin Policy and Cross Origin Resource Sharing Solves the “Reading” Problem
Browser introduces:
- Same-Origin Policy (SOP)
- later CORS
Their purpose is:
“Can JavaScript from one website read data from another website?”
Example:
fetch("https://bank.com/account")If this code runs on:
https://evil.comthe browser blocks access to the response.
Why?
Because otherwise malicious sites could steal:
- bank balances
- emails
- private messages
- personal information
So CORS mainly protects:
Reading responsesNOT sending requests.
Browsers Still Allow Many Cross-Origin Requests
This surprises many developers.
Browsers still allow websites to send many cross-origin requests because the web depends on it.
Examples:
<img src=https://other-site.com/image.png><script src=https://cdn.com/lib.js><form action="https://bank.com/pay">If browsers blocked all cross-origin requests:
- the web would stop working
So browsers chose this rule:
| Allowed | Blocked |
|---|---|
| Sending many requests | Reading sensitive responses |
And this is exactly why CSRF exists.
What CSRF Really Is
CSRF means:
A malicious website tricks your browser into sending an authenticated request to another wbsite where you are already logged in.
The attacker does NOT steal your cookie.
The browser sends it automatically.
Example CSRF Attack
You are logged into:
https://bank.comThen you visit:
https://evil.comThe attacker page contains:
<form action="https://bank.com/transfer" method="POST">
<input name=amount value="1000">
<input name=to value="attacker">
</form>
<script>
document.forms[0].submit();
</script>Your browser sends:
POST /transfer
Cookie: session=abc123The bank sees:
- valid session cookie
- authenticated user
and processes the transfer.
Important Thing to Notice
The attacker never:
- stole your cookie
- read the response
- bypassed login
They simply abused browser behavior.
Why CORS Does NOT Stop This
This is where most confusion happens.
People think:
“CORS blocks cross-origin requests.”
No.
CORS maily blocks:
JavaScript reading the responseThe request itself is often still sent.
So in the CSRF attack:
- browser sends the request
- cookies are included
- server processes it
Only AFTER that does CORS say:
“evil.com cannot read the response.”
But the damage already happened.
- Money transferred
- Password changed
- Email updates.
That's why:
CORS is NOT CSRF protection.
Simple Requests vs Non-Simple Requests
Browsers classify requests into two types.
Simple Requests
These are considered "normal web behavior".
Examples:
- GET
- POST forms
- Standard form submissions
Allowed content types:
application/x-www-form-urlencodedmultipart/form-datatext/plain
Simple requests:
- are sent immediately
- include cookies
- do NOT need preflight
This makes them ideal for CSRF attacks
Why HTML Forms Are Dangerous
HTML forms are limited.
They:
- cannot send JSON
- cannot add custom headers
- cannot use PUT/PATCH easily
But they CAN:
- send POST requests
- send cross-origin requests
- include cookies automatically
And forms use:
application/x-www-form-urlencodedwhich qualifies as a simple request.
So browsers send them immediately.
No permission checks first.
When CORS Indirectly Helps
Now comes the subtle part.
Suppose your API only accepts:
Content-Type: application/jsonA normal HTML form cannot send JSON.
To send JSON, attacker must user JavaScript fetch:
fetch("/transfer", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
})This becomes a NON-simple request.
So browser first sends a:
OPTIONSpreflight request.
Browser asks server:
“Does this server allow cross-orign JSON requests?”
If server says no:
- browser blocks the real request completely.
So JSON APIs are naturally harder to CSRF.
Anatomy of a CSRF Attack
A CSRF attack typically involves four steps:
Step 1: Victim Logs Into Target Site
Example:
https://bank.comServer creates:
Set-Cookie: session_id=abc123The browser stores the cookie.
Step 2: Victim Visits Malicious Site
Example:
https://evil.comThe user may arrive via:
- Email link
- Advertisement
- Social engineering
- Forum post
- Phishing message
Step 3: Malicious Site Triggers Request
The malicious page silently submits:
<form action="https://bank.com/transfer" method="POST">
<input type=hidden name=amount value="5000">
<input type=hidden name=recipient value="attacker">
</form>
<script>
document.forms[0].submit();
</script>Step 4: Browser Automatically Includes Cookie
The browser sends:
POST /transfer
Host: bank.com
Cookie: session_id=abc123The request succeeds because:
- Cookie is valid
- User authenticated.
Types of CSRF Attacks
1 GET-Based CSRF
Simplest form.
Example:
<img src=https://bank.com/delete-account>When the browser loads the image:
- Request is sent automatically
Dangerous if state-changing actions use GET.
Why GET Requests Should Never Change State
HTTP standards define:
- GET = safe and idempotent
- POST/PUT/DELETE = State-changing
Violating this principle creates major CSRF risk.
Bad:
GET /delete-user?id=10Good:
POST /delete-user2 POST-Based CSRF
More common and dangerous.
Uses hidden forms:
<form action="https://bank.com/change-password" method="POST">
<input type=hidden name=password value="hacked123">
</form>Auto-submitted via JavaScript.
3 JSON CSRF
Modern APIs may accept JSON requests.
Attacker abuse:
- Weak content validation
- Browser quirks
- Misconfigured APIs
Example:
fetch("https://api.bank.com/transfer", {
method: "POST",
credentials: "include",
body: JSON.stringify({...})
})Real World Example
Suppose
https://socialmedia.com/change-emailrequires only:
POST /change-email
Cookie: session=abc123Attacker creates:
<form action="https://socialmedia.com/change-email" method="POST">
<input type=hidden name=email value="attacker@mail.com">
</form>
<script>
document.forms[0].submit();
</script>Victims visits attacker page.
Result:
- Email changed
- Account takeover possible
Leave a comment
Your email address will not be published. Required fields are marked *
