What is CSRF (cross site request forgery)?

What is CSRF (cross site request forgery)?

A csrf attack tricks a logged in user’s browser into sending a request they never meant to send. The browser attaches the victim’s session cookie automatically, so the target app sees a normal, authenticated request and acts on it. Cross site request forgery, often written CSRF, abuses the gap between who clicked and what the server thinks happened.

What a csrf attack actually is

CSRF works because of one browser habit: cookies travel with every request to the site they belong to. If you are logged into Acme Notes in one tab, your session cookie goes out with any request your browser makes to acmenotes.example, no matter which page or which site started that request.

An attacker cannot read your cookie. They do not need to. They only need your browser to fire a request, and the browser supplies the cookie on its own. The server reads the cookie, sees a valid session, and trusts the request. This is the trap.

CSRF is not about stealing your session. It is about borrowing it for one request while you are not looking.

How the browser auto sends cookies

Say you log into Acme Notes and get a cookie named session=abc123. From that point, every request to acmenotes.example carries Cookie: session=abc123. A form submit, an image load, a script, a redirect: the cookie rides along. The browser does not ask whether the page that triggered the request is Acme Notes or some random blog. That ambient cookie is what an attacker reaches for.

A concrete example: changing a victim’s email

Acme Notes lets a user change their account email by posting to /account/email with one field, new_email. The endpoint checks the session cookie and nothing else. That single weak assumption, the cookie alone proves intent, is all a csrf attack needs.

The attacker builds a page and emails the victim a link, or hides it inside an ad. The victim, still logged into Acme Notes in another tab, opens the page. This form submits itself the instant the page loads:

<!-- evilpage.example/win.html -->
<form id="x" action="https://acmenotes.example/account/email" method="POST">
  <input type="hidden" name="new_email" value="attacker@evil.example">
</form>
<script>document.getElementById("x").submit();</script>

No click is needed. On load, the browser posts to Acme Notes and attaches session=abc123 because the request goes to acmenotes.example. The server sees a valid session, updates the email to attacker@evil.example, and now the attacker can trigger a password reset and take the account. The victim saw a blank page.

Why it works: ambient authority

The flaw is ambient authority. The session cookie acts as standing permission that applies to any request, regardless of where the request came from. The server proves who you are but never checks whether you meant this. CSRF lives in that missing check.

What makes a request CSRFable

Not every endpoint is a target. A request is exposed when all three of these hold:

  • It changes state. Updating an email, transferring funds, deleting a note, adding an admin. Read only endpoints leak nothing useful through CSRF on their own.
  • It authenticates by cookie alone. If the session rides only in an auto sent cookie, the browser hands it over for free. Endpoints that require a token in a custom header are much harder to forge from another origin.
  • It is predictable. The attacker must know the method, the URL, and the field names in advance. POST /account/email with one field new_email is easy to guess and easy to forge.

Flip any one of these and the attack gets harder. Defenses below break the second and third.

Defenses against a csrf attack

Synchronizer tokens (anti CSRF tokens)

The server generates a random token tied to the session, embeds it in every form, and requires it back on every state changing request. The attacker’s page cannot read that token, because the same origin policy blocks it from reading Acme Notes pages, so the forged request arrives without a valid token and the server rejects it.

# server side check, in plain pseudocode
token_from_form = request.body["csrf_token"]
token_for_session = session["csrf_token"]

if not token_from_form or token_from_form != token_for_session:
    reject(403)   # missing or wrong token, drop the request
else:
    process_email_change()

Token randomness matters. The token must be long and unpredictable, drawn from a cryptographically secure random source and unique per session. If the token is a counter, a timestamp, or a hash of the username, the attacker can compute it and include it in the forged form. A guessable token is no protection at all.

SameSite cookies

Mark the session cookie SameSite=Lax or SameSite=Strict. The browser then withholds the cookie on cross site requests. With SameSite=Strict, a POST from evilpage.example to acmenotes.example carries no session cookie, so the forged request lands as an anonymous one and fails. Lax still blocks cross site POSTs while allowing top level navigations, which suits most apps. Set this, and also keep tokens, because older browsers and some flows still slip through. You can confirm a cookie actually carries SameSite, Secure, and HttpOnly with our free security headers and CSP analyzer.

Checking Origin and Referer

State changing requests carry an Origin header, and often a Referer, that name the page that started them. The server can reject any request whose Origin is not its own. A forged request from evilpage.example shows Origin: https://evilpage.example, which fails the check. Treat this as a second layer, not the only one, since a missing header should be handled with care rather than waved through.

Why CORS is not a CSRF defense

This one trips people up. CORS controls whether JavaScript on one origin may read the response from another origin. CSRF does not care about reading the response. The damage, changing the email, is done by the request itself the moment the server processes it. The attacker never needs to see the reply. A restrictive CORS policy does not stop the browser from sending the cross site request with cookies attached, so it does nothing against a csrf attack. Treat CORS and CSRF as separate problems. That said, CORS has its own failure mode in the other direction, where response headers expose authenticated data to any origin; our free CORS misconfiguration checker flags those dangerous combinations.

A short checklist

  • Require an anti CSRF token on every state changing request, and make it random per session.
  • Set SameSite on session cookies.
  • Validate Origin on writes as a backup.
  • Do not lean on CORS for this. It guards reads, not writes.
  • Keep read endpoints read only, so a GET never changes state.

Want more on the access boundaries attackers probe, from sessions to permissions? Read the access control posts.

Closing

CSRF is a logic gap, not a payload. The server trusts a cookie as proof of intent, and an attacker borrows that trust for one request. The fix is to prove intent on every write with an unpredictable token, withhold cookies on cross site requests, and check where the request came from. This is exactly the kind of assumption, the cookie alone means the user meant it, that an autonomous researcher built to test how an app really behaves is made to find. To see how UnboundCompute approaches that, read about.

Frequently asked questions

What is a CSRF attack?

A CSRF attack tricks a logged in user’s browser into sending a request they never meant to send. The browser attaches the victim’s session cookie automatically, so the target app sees a normal authenticated request and acts on it. See the OWASP CSRF page for more background.

How do you prevent CSRF?

Require an anti CSRF token on every state changing request, drawn from a secure random source and unique per session, because the attacker’s page cannot read it. Set SameSite on session cookies so the browser withholds them on cross site requests, and validate the Origin header on writes as a backup layer.

Does CORS protect against CSRF?

No. CORS controls whether JavaScript on one origin may read the response from another origin, but CSRF does not care about reading the response, since the damage is done the moment the server processes the request. A restrictive CORS policy does nothing to stop the browser from sending a cross site request with cookies attached, so treat CORS and CSRF as separate problems.

What makes a request vulnerable to CSRF?

Three things have to hold at once. The request changes state, like updating an email or transferring funds, it authenticates by cookie alone, and it is predictable enough that an attacker can guess the method, URL, and field names in advance. Break any one of these and the attack gets much harder.