CSRF: Making the Browser Betray the User
Day 7 put your code in the victim’s browser. Today you do something quieter. You do not run code on the target site at all. You get the victim’s browser to send a request that the site trusts, because the browser attaches the victim’s own session to it. Cross Site Request Forgery, CSRF, is the bug where the site cannot tell whether the user meant to click.
This is the fifth vulnerability class in the series. If SQLi is data becoming SQL and XSS is data becoming JavaScript, CSRF is a different failure: the server trusts who sent the request but never checks whether they intended it.
Inside scope only, and with your own accounts. CSRF changes state: emails, passwords, settings, money. You prove it by acting on an account you own, never on a real user.
What CSRF actually is
Browsers attach cookies automatically. If you are logged in to bank.example and a page on any other site makes a request to bank.example, the browser sends your session cookie along with it. The site sees a valid session and processes the request.
So if an attacker can make your browser send “change email to attacker@evil.test” to a site where you are logged in, and the site relies only on the cookie to decide it is you, the email changes. Next comes a password reset to the new address. That chain is how a boring “change email” CSRF becomes full account takeover.
Three conditions must all be true:
A state changing action worth abusing (email, password, 2FA, payment, role, delete)
Cookie based authentication, where the browser sends credentials automatically
No unpredictable value the attacker cannot supply, such as a valid CSRF token
If any one is missing, there is usually no CSRF. Most of your hunting is checking which one is missing.
Why you still find it in 2026
Two big defenses changed the landscape.
CSRF tokens. A random value tied to the user’s session, included in each form and checked by the server. The attacker cannot read it from another origin, so the forged request fails.
SameSite cookies. Modern browsers treat cookies without an explicit SameSite attribute as Lax by default. Lax blocks cookies on most cross site subrequests, such as a form POST from another site. That killed a huge number of classic CSRF bugs overnight.
So why does CSRF still pay? Because defenses get applied unevenly.
Developers add tokens to the main forms and forget secondary endpoints, newer APIs, and mobile backends
Tokens exist but are not actually validated, or are validated loosely
Apps set SameSite=None to make embedded or cross domain features work, which removes the safety net
Lax still sends cookies on top level GET navigations, so state changing GET endpoints stay exposed
Cookies set by a sibling subdomain or a different path can sit outside the browser’s expectations
Chains with XSS or open redirects turn a “blocked” CSRF into a working one
The pattern from earlier days holds: bugs cluster in the forgotten parts. Your recon of old endpoints, API versions, and integration flows is the hunting ground again.
The variants worth knowing
Classic form CSRF. A hidden form on your page auto submits to the target. Works when the cookie is sent cross site and there is no token.
GET based CSRF. A state change triggered by a GET request, such as an image tag or link. Lax cookies still travel on top level navigation, so this survives SameSite defaults.
JSON CSRF. The endpoint accepts JSON, and the developer assumed that meant safe. If the server does not enforce the Content-Type, you can sometimes send the JSON body as text/plain from a form. If it does enforce it, a cross origin fetch triggers a CORS preflight and fails, unless CORS is misconfigured.
Login CSRF. You force the victim to log in as the attacker’s account. Everything they then do, such as saving a card, lands in your account. Lower severity on its own, but a real finding when it leads to data capture.
OAuth and account linking CSRF. The flow that connects a social or payment account lacks a proper state parameter, or the state is not bound to the session. You make the victim link your external account to theirs, or the reverse. This is one of the highest value places to look.
Logout, delete, and settings CSRF. Usually low severity unless the action is destructive and irreversible.
WebSocket hijacking. A cross site WebSocket handshake that relies on cookies and does not check Origin. The attacker’s page then talks to the socket as the victim.
Disclosed reports worth reading
Real reports show where the value sits.
Shopify, CSRF when connecting PayPal as a payment provider. The connection flow let an attacker link their PayPal account to a victim’s store, and the underlying issue was a CSRF token with a very long lifetime. Report: https://hackerone.com/reports/807924
Twitter, full account takeover using CSRF. Reported by yipman and paid $5,040. Search the title on HackerOne Hacktivity: “[CRITICAL] Full account takeover using CSRF”. The lesson is the escalation: a state change on an account recovery related setting, not a trivial toggle.
Khan Academy, password set via CSRF. Reported by tomoh. For users who signed up with Google or Facebook and never set a password, an attacker could set one via a forged request. Press coverage by Threatpost lays out the mechanics.
TikTok, XSS chained with CSRF for one click account takeover. The researcher injected a script into a vulnerable URL parameter to trigger the CSRF, and TikTok rated it high severity. This is the chain pattern: a bug that looks blocked becomes critical when combined.
Read the Shopify report end to end first. Note the endpoint, what the token did and did not protect, and how impact was demonstrated.
How to hunt it, step by step
Step 1: list state changing actions. From your recon and your own use of the app, write down every action that changes something: email, password, phone, 2FA, profile, billing, API keys, roles, deletions, account linking.
Step 2: check the authentication model. Does the app use cookies, or a bearer token in a header? Pure header based tokens that the browser does not attach automatically are generally not CSRF candidates. Cookie based sessions are.
Step 3: check the cookie. Look at the session cookie’s SameSite attribute. Explicit None, or a Lax cookie with a GET based action, keeps you in the game.
Step 4: look for the token. Capture the request. Is there a CSRF token, a custom header, or an Origin check? If there is nothing unpredictable in the request, that is your lead.
Step 5: test the token, if there is one. Use two accounts you own, attacker and victim. Try:
Remove the token parameter entirely
Send an empty value
Use a token from your attacker account in the victim’s session
Reuse an old token, and check expiry
Change the method, for example POST to GET, and see if the check disappears
Change the Content-Type
Strip or alter the Referer and Origin headers
Check whether the token is tied to the session or just to the existence of any token
Any one of these working means the token is decoration.
Step 6: build a minimal proof. Host a static HTML page on a server you control, or open it locally. Keep it harmless and aimed at your own test account.
html
<!– PoC: run only against an account you own, on an in-scope target –>
<html>
<body>
<form id=“f” action=“https://target.example/account/email” method=“POST”>
<input type=“hidden” name=“email” value=“attacker-test@example.com”>
</form>
<script>document.getElementById(‘f’).submit();</script>
</body>
</html>
Log in as the test victim, open the page in the same browser, and confirm the change happened. Record a short video. That is your evidence.
Step 7: find the escalation. A changed display name is a low. A changed email, then password reset, is account takeover. Always ask what the action unlocks next. Severity is the end of the chain, not the first request.
Writing the CSRF report
Title: CSRF in [action] at [endpoint] allows [impact]
Summary: One sentence: which action, which endpoint, why the request is forgeable.
Steps to Reproduce:
Log in as test victim account A.
Open the attached PoC page in the same browser.
Observe [email changed / setting modified / account linked] without any victim interaction beyond visiting the page.
(Exact request, the missing or bypassed protection, the PoC file, screenshot or video.)
Impact: An attacker can change [X] on any logged in user who visits a malicious page. If this leads to takeover (email change followed by reset), state the full chain.
Remediation: Enforce a per session, unpredictable CSRF token on all state changing requests. Set SameSite=Lax or Strict on session cookies, validate Origin and Referer, and never perform state changes on GET.
Strong reports share three traits. They show the browser behavior (which browser, what SameSite did), they prove the action on an account the researcher owns, and they chain to the highest honest impact. Many programs close low impact CSRF as informational, so the escalation step is what gets you paid.
What programs often reject
Know this before you spend time on it.
CSRF on logout or on actions with no security impact
CSRF that needs the victim to already be an attacker controlled session
Findings that only work in browsers or settings no longer in use
Anything blocked by default SameSite Lax where you have not shown a bypass
Login CSRF with no demonstrated consequence
Check the program policy. Some list these as out of scope explicitly.
The mindset: trust the request or trust the intent
SQLi and XSS were about data crossing into code. CSRF is about a different boundary: between a request the user meant to send and a request the browser sent on their behalf. Whenever you see a state change protected only by “the cookie is valid,” ask what proves the user wanted it. That question also catches issues in OAuth flows, WebSockets, and APIs that mix cookie and token auth.
Homework for Day 8
From your recon, list every state changing action and mark which use cookie based sessions.
Inspect the SameSite attribute on the session cookie for each in scope host.
For each action, check for a token, a custom header, or an Origin check. Where there is none, build a minimal PoC and test it with two accounts you own.
Where a token exists, run the bypass checklist: remove, empty, swap, reuse, method change, content type change.
Read the Shopify report (https://hackerone.com/reports/807924) end to end, then find the Twitter and Khan Academy writeups. For each, note the action, the missing protection, and how the impact was escalated.
A confirmed CSRF that ends in account takeover is typically high severity. Document it with the PoC, the browser used, and the full chain.

Day 9 moves to bugs where the server fetches things on your behalf, Server Side Request Forgery, and how it reaches internal networks and cloud metadata.