CSRF vs. XSS: What's the Difference?
CSRF forges a request using a victim's login session; XSS runs the attacker's own code inside the victim's browser. Different mechanisms, different fixes.
CSRF and XSS are both browser-based web attacks, but they work in opposite directions. Cross-site request forgery tricks a victim’s browser into sending a request the victim never intended, relying on cookies the browser attaches automatically. Cross-site scripting gets the attacker’s own JavaScript to execute inside the victim’s browser, on the victim’s own origin. One never runs any code at all; the other runs code with full access to the page. Confusing the two — or defending against only one — is one of the more common gaps in a web app’s security posture.
The core mechanism, side by side
CSRF doesn’t need to inject anything into the target site. It just needs the victim’s browser to make a request — via an auto-submitting form, an image tag, or a fetch call from a completely different, attacker-controlled page — to an endpoint on the target site. Because browsers attach cookies to a request based on the destination domain, not the page that triggered the request, the forged request arrives looking authenticated.
XSS works the opposite way: the attacker’s payload has to actually land inside the target site’s own HTML or JavaScript context — through an unsanitized comment field, a URL parameter reflected into the page, or a stored value rendered without escaping. Once it runs, it’s not “borrowing” the user’s session the way CSRF does; it has direct, first-class access to everything that page’s JavaScript can see, including the DOM, any tokens stored in memory, and — if cookies aren’t marked HttpOnly — the cookies themselves.
A comparison
| CSRF | XSS | |
|---|---|---|
| Does attacker code run on the target site? | No | Yes |
| What’s exploited | Automatic cookie attachment on cross-origin requests | Unsanitized input rendered as HTML or executed as script |
| Where the malicious content lives | An entirely separate, attacker-controlled page | Inside the target site itself (reflected, stored, or DOM-based) |
| Can it read the page’s content or cookies? | No — it can only trigger a request, not observe the response | Yes — full access to the DOM and any accessible cookies |
| Typical target | State-changing endpoints (POST, PUT, DELETE) | Anywhere untrusted input reaches the page unescaped |
| Primary defenses | CSRF tokens, SameSite cookies, origin checks | Output encoding, Content Security Policy, input sanitization |
Stopped by HttpOnly cookies? | No — the browser still attaches the cookie to the request | Partially — prevents document.cookie theft, not the injection itself |
That last row trips people up: HttpOnly is often described as an XSS mitigation, and it is one — it stops injected JavaScript from reading the cookie directly — but it does nothing for CSRF, since the browser still attaches an HttpOnly cookie to outgoing requests automatically. The two problems genuinely need separate defenses; hardening one doesn’t move the needle on the other at all.
Why a site can be vulnerable to one and not the other
Because the attack surfaces don’t overlap, it’s entirely possible to build an app immune to one and exposed to the other. A site that escapes every piece of user-generated output correctly and sets a strict Content Security Policy can be effectively closed to XSS while still forging cross-site requests freely, if its state-changing endpoints check nothing but the session cookie. Conversely, an API that requires an explicit JWT in an Authorization header rather than a cookie is naturally resistant to CSRF — a forged cross-site request has no way to attach a custom header the browser wouldn’t send on its own — but that same API is still fully exposed to XSS if a script injected into the frontend can just read the token out of memory and use it directly.
SameSite cookies close a lot of the historical CSRF surface by preventing the browser from sending a cookie along with most cross-site requests in the first place — see cookie attributes for how Strict, Lax, and None differ. It’s a meaningful default improvement, but it doesn’t touch XSS at all, since XSS doesn’t rely on cross-site cookie behavior to begin with — the malicious code is already running same-site.
A practical way to tell which one you’re looking at
When triaging a reported vulnerability, one question usually settles it: does the exploit require getting content into the target page, or does it only require getting the victim’s browser to visit an attacker-controlled page? If the proof of concept is a snippet that has to be stored in a comment, a profile field, or a URL that gets reflected into the target site’s own HTML, that’s XSS. If the proof of concept is an entirely separate HTML file — hosted anywhere — with a form or script that just points at the target site’s endpoints, that’s CSRF. Clickjacking is a third, related pattern worth knowing alongside both: it doesn’t inject code or forge cookie-authenticated requests either, it tricks a user into clicking something real on the target site by hiding it under deceptive content in a transparent frame.
A minimal defense checklist
- Escape and encode all user-generated output at render time; treat any input that reaches HTML, an attribute, or a script context as hostile until proven otherwise. This is the primary XSS defense.
- Set a
Content-Security-Policythat restricts script sources, as a second layer if output encoding is ever missed somewhere. - Require CSRF tokens on every state-changing request, and set cookies with
SameSite=LaxorStrictas a baseline default. - Mark session cookies
HttpOnlyandSecure— necessary hygiene against XSS-driven cookie theft, but understand it does nothing for CSRF on its own. - If the app is a single-page app using token-based auth instead of cookies, keep the token out of
localStoragewhere an XSS payload can trivially read it, and prefer sending it explicitly in headers rather than storing it somewhere ambient.
The takeaway
CSRF forges a request that rides on a victim’s already-authenticated session, without ever running code on the target site; XSS gets the attacker’s own code running inside the target site, with direct access to the page and anything reachable from it. They share the label “cross-site” and little else — SameSite cookies and CSRF tokens address one, output encoding and a solid Content Security Policy address the other, and a genuinely secure app needs both, not whichever one is easier to bolt on first.
Keep reading
Chisato · · 4 min read What Is a Man-in-the-Browser Attack?
A man-in-the-browser attack uses malware inside the browser itself to alter what a user sees and submits, bypassing HTTPS and session protections entirely.
Chisato · · 4 min read Magic Links: How Passwordless Email Login Works
A magic link authenticates a user by emailing a single-use, expiring URL instead of checking a password. How the flow works and its real tradeoffs.
Chisato · · 4 min read Session Hijacking Explained
Session hijacking steals a valid session token to impersonate a logged-in user without a password. How attackers do it and how to stop it.