CSRF lets an attacker use your own browser against you — triggering authenticated actions on sites you're logged into, without ever touching your credentials. Here's how it works and how to find it.
You're logged into your bank. In another tab, you visit a sketchy forum. The forum loads an invisible image tag that points to your bank's transfer endpoint. Your browser, being helpful, sends your bank cookies along with the request. The transfer goes through.
That's Cross-Site Request Forgery. The attacker didn't steal your credentials. They didn't break your session. They just made your browser do something you didn't intend — using authentication you already had.
Browsers automatically attach cookies to every request made to the domain that set them. This is what keeps you logged in — your session cookie goes along with every request to the site, including requests initiated by other pages.
CSRF exploits this. An attacker hosts a page that, when loaded, causes your browser to make a request to a target site using your existing session. The target site sees a valid, authenticated request — it has no way to tell that it was triggered by a third-party page, not by you.
<!-- Attacker's page: triggers a state-changing request silently -->
<img src="https://bank.com/transfer?to=attacker&amount=1000" width="0" height="0">
<!-- Or with a form for POST requests -->
<form id="csrf" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.getElementById('csrf').submit();</script>The victim visits the attacker's page while logged into their bank. Their browser sends the request. The bank processes it. The money moves.
Three conditions have to be true simultaneously:
If all three hold, any page on the internet can trigger state-changing actions on behalf of any logged-in user who visits it.
GET-based state changes are the worst case: a single <img> tag is enough. But POST requests are almost as easy to exploit — a hidden form with auto-submit takes five lines of HTML.
Using POST doesn't protect against CSRF. Only explicit origin verification does.
Content-Type: application/json without triggering a preflight. But if an endpoint accepts text/plain or application/x-www-form-urlencoded, it's still exploitable — and many endpoints do.Look for state-changing requests (anything that modifies data — account settings, password, email, payment, permissions) and check whether they include any unpredictable token that an attacker couldn't forge.
# In Burp Suite: capture a state-changing request POST /account/email HTTP/1.1 Host: target.com Cookie: session=abc123 Content-Type: application/x-www-form-urlencoded email=victim@example.com # No CSRF token in the body or headers? # Test it: replay this request from a different origin # If it succeeds, the endpoint is vulnerable
The test is simple: can you replay the request from a different origin without adding any secret the attacker couldn't know? If yes — it's vulnerable.
Things that do NOT prevent CSRF:
Referer header (easy to strip, sometimes absent)Content-Type header (some types bypass CORS preflight)Some applications implement CSRF tokens but do so incorrectly. Common weaknesses:
# Test 1: Remove the CSRF token entirely POST /account/email email=attacker@evil.com # (no csrf_token field) # → Does it succeed? Token not required. # Test 2: Use an empty token POST /account/email email=attacker@evil.com&csrf_token= # → Does it succeed? Token validation broken. # Test 3: Use your own valid token for another user's request # Get a token from your own session, use it in a cross-account attack # → Does it succeed? Token not tied to session.
A CSRF token is a random, unpredictable value generated server-side and tied to the user's session. It's included in every state-changing form or request. The server validates it before processing.
<!-- Form with proper CSRF token --> <form action="/transfer" method="POST"> <input type="hidden" name="csrf_token" value="a8f3d2...randomly generated..."> <input type="text" name="amount"> <button type="submit">Transfer</button> </form>
The attacker can't read the token from your page (same-origin policy prevents it), and they can't guess it (it's cryptographically random), so they can't forge a valid request.
Modern browsers support the SameSite cookie attribute, which controls whether cookies are sent on cross-site requests:
SameSite=Strict — cookie never sent on cross-site requests (most restrictive)SameSite=Lax — cookie sent on top-level navigation (clicking a link) but not sub-requests (images, forms). This is the browser default in Chrome.SameSite=None; Secure — cookie always sent (old behaviour, requires HTTPS)SameSite=Laxas a browser default has significantly reduced CSRF exposure, but it's not a complete fix. GET-based state changes are still exploitable, and top-level navigation attacks (a link that triggers a GET-based action) still work.
CSRF vulnerabilities consistently appear in bug bounty programmes, particularly on:
Impact scales with what the vulnerable action does. A CSRF against a log-out endpoint is low severity. A CSRF against a password-change or fund-transfer endpoint is critical.
Everything in this post has a live lab on hackr.gg. Spin up a vulnerable machine and exploit it yourself — no setup, no VPN, runs in your browser.
Open CSRF Attacks course →