SQL Injection vs. XSS vs. CSRF: 1 Million Users in 20 Hours and a £400,000 Fine
On October 4, 2005, a MySpace user posted a profile containing a few lines of JavaScript that slipped past the site's filters. Anyone who viewed that profile ran the script without knowing it, and the script copied itself onto the viewer's own profile. Within about 20 hours, over one million users had run it, and MySpace temporarily shut the site down to fix the flaw. The worm was harmless, but it proved something alarming: a single input-handling mistake could turn a website's own visitors into its distribution network.
A decade later, in October 2015, the UK telecom TalkTalk lost the personal data of about 157,000 customers, including bank account numbers and sort codes for roughly 15,700 of them. The attacker used SQL injection against three vulnerable web pages sitting on a database TalkTalk had inherited in a 2009 acquisition and never fully inventoried. The regulator found the same flaw had been exploited successfully twice earlier that year, and fined the company a then-record £400,000.
And in 2008, Princeton researchers showed a third kind of attack hitting ING Direct, YouTube, MetaFilter, and The New York Times. An attacker's web page could make a logged-in visitor's own browser perform actions on those sites, such as opening an account at a bank, without the visitor ever clicking anything on the real site. That's cross-site request forgery, or CSRF.
These three attacks get lumped together as "web security stuff," but they fool three different victims. SQL injection fools the database. Cross-site scripting (XSS) fools the user's browser. CSRF fools the website itself. Once you see who's being fooled, the defenses stop feeling like a random checklist. This guide breaks down each attack, where the real numbers come from, and the one control that matters most for each.
If you're newer to security work, our Penetration Testing and Auth & Identity courses cover the fundamentals this comparison builds on.
This post originally appeared on the Ciphemic Academia blog.
The Short Version
- SQL injection happens when user input gets treated as part of a database command. The attacker's text becomes code the database runs.
- Cross-site scripting (XSS) happens when a site puts untrusted input into a page without encoding it. The attacker's script runs in other users' browsers, with the site's privileges.
- CSRF happens when a site trusts any request that arrives with a valid session cookie. A malicious page makes the victim's browser send a request the victim never intended.
OWASP's Top 10:2025 reflects how differently these behave. It places Injection at A05 and notes that XSS shows up very often but with lower average impact (more than 30,000 CVEs), while SQL injection shows up less often but with higher impact (more than 14,000 CVEs). CSRF is mapped into Broken Access Control, OWASP's number one category, where 100% of tested applications had some form of the problem.
If you want one default: learn each attack by asking who is being fooled, then memorize one primary defense for each: parameterized queries for SQL injection, output encoding for XSS, and anti-CSRF tokens plus SameSite cookies for CSRF.
What All Three Are Actually Trying to Solve (For the Attacker)
Before the differences, the shared root cause:
- Misplaced trust: each attack works because an application trusts something it shouldn't, whether that's user input, page content, or a browser's cookies
- Ordinary features: the vulnerable code is usually a search box, a comment field, or a "transfer" button, not an exotic edge case
- Well-known fixes: every one of these has had mature, documented defenses for years, which is why breaches that involve them draw regulator attention
- Cheap to attempt: automated tools and copy-paste payloads let low-skill attackers try them at scale. The TalkTalk case involved a teenager using an off-the-shelf SQL mapping tool
The skill isn't memorizing payloads. It's recognizing where your application crosses a trust boundary and handling that crossing safely.
SQL Injection
What it actually is: an attack where input intended as data ends up interpreted as part of a SQL command. If your code builds queries by pasting user input into a string, an attacker can change what the query means.
A small example (vulnerable code, then the fix):
# Vulnerable: input becomes part of the SQL itself
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)
# If email is: ' OR '1'='1 the WHERE clause matches every row
# Safe: a parameterized query keeps input as data, never as SQL
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
Why it's dangerous:
- Direct access to the data: a successful injection can read, change, or delete records, sometimes including data the page never meant to show
- High impact when it lands: OWASP describes SQL injection as lower frequency but higher impact than XSS, which matches what TalkTalk experienced: 157,000 customers affected and a regulator's record fine
- Easy to automate: attackers can point scanning tools at thousands of sites and see who's vulnerable
Why it keeps happening:
- Legacy and inherited systems: TalkTalk's vulnerable pages sat on outdated, unpatched database software it hadn't known it owned
- String-building habits: quick scripts and raw queries inside otherwise safe frameworks reintroduce the flaw
- Ignored warnings: the regulator highlighted that the same vulnerability had been exploited twice earlier in 2015
How to defend:
- Parameterized queries or prepared statements, everywhere, for every database call
- Safe ORM APIs, and extra care wherever you drop to raw SQL
- Least-privilege database accounts, so a compromised query can't reach everything
- Patch and inventory your systems, including acquired ones
- Input validation as a secondary layer, never as the primary defense
Who is fooled: the database, which can't tell where the developer's SQL ends and the attacker's begins.
Cross-Site Scripting (XSS)
What it actually is: an attack where untrusted input is included in a web page without proper encoding, so the browser treats it as script. The script runs with the site's privileges inside the victim's browser. It comes in three common forms: stored (saved on the server, such as a comment), reflected (bounced back from a request, such as a search parameter), and DOM-based (created by client-side code).
A small example (vulnerable template, then the fix):
<!-- Vulnerable: the comment is inserted into the page as raw HTML -->
<p>Latest comment: {{ comment|safe }}</p>
<!-- A comment containing <script>alert('XSS')</script> runs in every visitor's browser -->
<!-- Safe: output is HTML-encoded, so the text is displayed, not executed -->
<p>Latest comment: {{ comment }}</p>
<!-- Jinja2 and most modern frameworks auto-escape by default -->
Why it's dangerous:
- It runs as your site: injected script can act as the logged-in user, read page content, and make requests on their behalf
- It spreads: the Samy worm showed a stored XSS flaw can propagate itself, reaching over one million MySpace users in 20 hours
- It's extremely common: more than 30,000 CVEs, which is why OWASP calls it high frequency, even if average impact per instance is lower than SQL injection
Why it keeps happening:
- Filters that miss cases: MySpace did filter user content, and the Samy author found a way to sneak JavaScript past the filters
-
Developers opting out of safe defaults: using raw-HTML escape hatches such as
|safein templates ordangerouslySetInnerHTMLin React - Many output contexts: HTML text, attributes, JavaScript, and URLs each need different encoding
How to defend:
- Context-aware output encoding, which modern frameworks do automatically unless you disable it
- A Content Security Policy, to limit what scripts the browser will run even if something slips through
-
HttpOnlycookies, so injected script can't read session cookies - A vetted sanitizer such as DOMPurify when you genuinely must allow user HTML
- Avoid building HTML from strings in your own front-end code
Who is fooled: the browser, which trusts script that appears to come from the site you're visiting.
Cross-Site Request Forgery (CSRF)
What it actually is: an attack where a malicious page makes a logged-in user's browser send a request to a trusted site. Because browsers automatically attach cookies, the site sees a valid session and assumes the user meant to do it. The attacker never needs to see the response; they just need the action to happen.
A small example (the attacker's page, using placeholder domains):
<!-- On a page the victim is lured to: a hidden form that submits itself -->
<form action="https://bank.example/transfer" method="POST">
<input type="hidden" name="to" value="attacker-account">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit()</script>
<!-- If the victim is logged in at bank.example and the site has no CSRF defense,
the request arrives with their cookies and looks legitimate -->
Why it's dangerous:
- It borrows the victim's identity: the request comes from the real browser with real cookies, so the site can't distinguish it from a genuine action
- No code runs on the target site: the Princeton researchers stressed that a site fully protected from XSS can still be open to CSRF if it takes no CSRF protections
- Real consequences: in their 2008 findings, one attack could open accounts and move funds at a bank, another exposed users' email addresses, another let an attacker take over a MetaFilter account through its password-reset feature
- HTTPS doesn't help: ING's use of SSL did nothing to prevent the attack
Why it keeps happening:
- Developers don't think about it: the researchers noted CSRF has been widely overlooked because of a lack of awareness
- State-changing actions on GET requests, or POST handlers with no verification of intent
- Cookie-based sessions by default: browsers send cookies automatically, which is exactly what the attack relies on
How to defend:
- Anti-CSRF tokens, a secret value tied to the user's session that the attacker's page cannot know, verified on every state-changing request
-
SameSitecookies, which tell the browser not to send cookies on most cross-site requests. Modern browsers default many cookies to a restrictive setting, but don't rely on that alone -
Check the
OriginorRefererheader as an additional layer - Re-authenticate for sensitive actions such as changing a password or moving money
- Use your framework's built-in protection rather than inventing your own
Classic CSRF mostly targets cookie-based sessions. APIs that authenticate with a token the client must attach explicitly, rather than a cookie the browser sends on its own, aren't open to it in the same way; our JWT vs. Sessions post covers that trade-off.
Who is fooled: the website, which trusts any request that arrives with a valid session cookie.
Side-by-Side Comparison
| SQL Injection | XSS | CSRF | |
|---|---|---|---|
| Who is fooled | The database | The user's browser | The website |
| What the attacker abuses | Input treated as SQL code | Input treated as page script | A browser's automatic cookies |
| Where the attacker's content runs | In the database | In the victim's browser | In no one's; the victim's browser just sends a request |
| Typical impact | Data theft, tampering, deletion | Session hijacking, account actions, worms | Unwanted actions as the victim |
| OWASP 2025 view | Injection (A05): low frequency, high impact, 14k+ CVEs | Injection (A05): high frequency, lower impact, 30k+ CVEs | Broken Access Control (A01) |
| Primary defense | Parameterized queries | Context-aware output encoding | Anti-CSRF tokens and SameSite cookies |
| Backup defenses | Least privilege, patching, validation | Content Security Policy, HttpOnly cookies | Origin checks, re-authentication |
| Famous case | TalkTalk, 2015 | Samy worm, 2005 | Princeton's 2008 findings (ING Direct, others) |
How the Three Fit Together
A quick way to hold all three in your head:
SQL injection: attacker → [web app] → database runs the attacker's SQL
XSS: attacker → [web app] → victim's browser runs the attacker's script
CSRF: attacker's page → victim's browser → [web app] acts on the victim's behalf
They also combine. A single XSS flaw can defeat CSRF protections, because script running inside the site's own pages can read anti-CSRF tokens and send valid requests. That's why fixing only one of these isn't enough, and why a threat model should ask all three questions: what input reaches my database, what input reaches my pages, and which of my actions can be triggered by a request alone.
How This Fits a Career Path
- Backend and full-stack engineers: parameterized queries and your framework's CSRF and XSS defaults are a baseline expectation, and missing them is a classic code review finding
- Application security engineers: you'll find and fix these in design reviews, code reviews, and automated scanning
- Penetration testers: these three are staples of web application testing, and our threat modeling vs. penetration testing post shows where testing fits in a security program
- DevSecOps roles: you'll wire scanners and dependency checks into delivery pipelines; see our DevSecOps career guide
- Interviews: "what's the difference between XSS and CSRF?" is one of the most common web security interview questions, and the "who is fooled" framing gives you a clear answer
How to Defend Against All Three Without Overthinking It
- Never build SQL by joining strings with user input. Use parameterized queries every time, and restrict database account permissions.
- Encode output by context and keep your framework's defaults on. Treat every raw-HTML escape hatch as a code review red flag, and add a Content Security Policy.
-
Protect every state-changing request with an anti-CSRF token and
SameSitecookies, and never change state on a GET request. - Patch and inventory everything, including acquired and forgotten systems. TalkTalk's breach started on infrastructure it didn't fully know it had.
- Test for all three regularly. Combine code review, automated scanning, and a penetration test, and only test systems you own or have written permission to test.
- Practice legally. Intentionally vulnerable training apps such as OWASP Juice Shop and DVWA are designed for exactly this.
A note on honesty: OWASP's CVE counts measure how many vulnerabilities have been reported across software, not how likely your specific application is to be affected. TalkTalk's total cost is reported at different figures across sources, with estimates ranging from about £42 million to about £60 million, so treat the £400,000 fine, the only official figure, as the anchor. The Samy worm and the 2008 Princeton findings are historical, and the affected sites fixed those flaws long ago. They're useful because they illustrate each class of flaw clearly, not because those exact bugs still exist.
Common Mistakes When Learning Web Security
- Trying to "sanitize" instead of parameterize. Stripping quotes or blocking keywords is fragile. Parameterized queries remove the problem at its root.
- Assuming HTTPS stops these attacks. HTTPS protects data in transit. It doesn't stop a database from running injected SQL, a browser from running injected script, or a site from honoring a forged request.
- Assuming XSS protection covers CSRF. The Princeton researchers made this exact point: they're different problems with different fixes.
-
Using raw-HTML escape hatches carelessly.
|safe,innerHTML, anddangerouslySetInnerHTMLbypass the encoding that protects you. - Changing state on GET requests. Links and image tags can trigger GET requests from any page, which makes forged requests trivial.
-
Leaving session cookies without
HttpOnlyandSameSite. Both flags are cheap and block whole classes of attack. - Ignoring inherited and legacy systems. A forgotten, unpatched database is exactly how a record fine started.
- Testing without permission. Only probe systems you own or are explicitly authorized to test.
Frequently Asked Questions
Which of the three is the most dangerous?
By typical impact per instance, SQL injection, since it can expose or alter the database directly. By sheer frequency, XSS. CSRF's danger depends on what actions your site lets a logged-in user perform. In practice, all three need fixing.
Does HTTPS prevent SQL injection, XSS, or CSRF?
No. HTTPS encrypts traffic between browser and server. All three attacks work through ordinary, correctly encrypted requests.
Can XSS be used to bypass CSRF protection?
Yes. Script running inside your own pages can read anti-CSRF tokens and send valid requests, which is why XSS defenses are also CSRF defenses in practice.
Is CSRF still a problem now that browsers default to SameSite cookies?
It's much less common than it was, but not gone. Defaults differ across browsers and cookie types, and some designs still need explicit tokens. Treat SameSite as one layer, not the only one.
Do ORMs make SQL injection impossible?
They greatly reduce it by parameterizing queries for you, but raw-SQL escape hatches and unsafe string-building inside an ORM can reintroduce it.
How can I practice these safely and legally?
Use deliberately vulnerable training apps such as OWASP Juice Shop or DVWA on your own machine, never a real website without written permission.
How do interviewers evaluate this topic?
Usually by asking you to explain the difference and the primary defense for each. Answering with "who is fooled" and one concrete fix per attack shows real understanding instead of memorized definitions.
Know Who Is Being Fooled
SQL injection fools the database, XSS fools the browser, and CSRF fools the website. Learn the attack, the single most important defense, and where your own application crosses each trust boundary. Explore the Penetration Testing and Auth & Identity courses to practice finding and fixing all three in safe, hands-on projects.
Top comments (0)