XSS Case Studies
Samy (MySpace 2005) — the first XSS worm. Twitter onmouseover (2010). eBay DOM XSS (2014). British Airways/Magecart (2018). The payloads, the root causes, the post-mortems.
Samy (MySpace 2005) — the first XSS worm. Twitter onmouseover (2010). eBay DOM XSS (2014). British Airways/Magecart (2018). The payloads, the root causes, the post-mortems.
From the first self-replicating XSS worm on MySpace to the Magecart supply-chain attack on British Airways, these case studies show how the same class of bug — untrusted data reaching a script execution context — has caused billions of dollars in damage across two decades.
The Samy worm (MySpace 2005) was the first XSS worm. Samy Kamkar found that MySpace filtered <script>, <body>, and onclick, but allowed onmouseover and CSSbackground: url(). He crafted a polyglot payload that combined CSS background: url(javascript:…) with onmouseover — when a user hovered over his profile, the payload added them as a friend and copied itself to their profile. Within 20 hours, over one million MySpace users were infected.
The Twitter onmouseover worm (2010) proved that even a 140-character tweet could carry an XSS payload. A user posted @<img src="x" onerror="…">. Twitter's text parser treated the tweet as plain text but failed to escape the content inside the <img> tag when rendering user profiles. Hovering over the tweet triggered the payload, retweeting the worm to the user's followers.
Click through each case study to see the payload, the root cause, the vulnerable code, and the fix. Each entry includes a side-by-side comparison of the vulnerability and its remediation.
First XSS worm. 1 million infected in 20 hours.
<div onmouseover="eval(fetch('/profile').then(x=>x.text()).then(t=>{/* copy payload */}))" style="background:url(javascript:alert(1))">hover</div>CSS background: url(javascript:…) bypassed MySpace filter. onmouseover was not blocked.
Switch from tag blocklist to context-aware HTML escaping. Use safe CSS property whitelist.
// MySpace blocked <script>, <body>, onclick // but missed CSS javascript: URLs and onmouseover sanitizer.blockTags(["script", "body"]); sanitizer.blockAttrs(["onclick"]); // onmouseover + CSS syntax passed through
// Context-aware sanitisation
import { sanitize } from "isomorphic-dompurify";
const clean = sanitize(input, {
ALLOWED_TAGS: ["b", "i", "u", "a"],
ALLOWED_ATTR: ["href"],
});
// CSS properties validated by whitelistIn 2014, security researcher Jann Horn discovered a DOM-based XSS on eBay's item listing page. The vulnerable JavaScript read the item category from the URL hash and wrote it into the page via innerHTML. The category value was not validated or escaped because it was assumed to come from eBay's own server — but the server never received the hash fragment. An attacker could craft a link like /item/123#<script>…</script>and send it via eBay's own messaging system.
The eBay case illustrates a recurring pattern: developers assume a value is safe because it originates from a trusted source, but the value reaches the browser through an attacker-accessible channel. In this case, the category parameter in the hash fragment was never meant to be user-controlled, but the application provided no server-side validation to enforce that assumption.
Each case study reinforces the same principle: context-aware escaping must happen at the output layer, not the input layer. MySpace filtered specific tags but the attacker found a polyglot. Twitter trusted the text parser but the browser parsed the event handler. eBay trusted the URL hash because the server did not control it. The fix is always the same — escape for the correct context at the point where the value enters the DOM.
// VULNERABLE — MySpace-style filter approach
const blocked = ['script', 'body', 'onclick'];
function isSafe(input: string): boolean {
return blocked.every((b) => !input.includes(b));
}
// Bypassed with onmouseover + CSS javascript: url
// VULNERABLE — eBay DOM XSS
const category = location.hash.slice(1);
document.getElementById('breadcrumb')!.innerHTML = category;
// SAFE — context-aware output escaping
const category = location.hash.slice(1);
document.getElementById('breadcrumb')!.textContent = category;
// textContent disables HTML parsing — no script execution
// SAFE — validate and sanitise at the sink
function safeSetHtml(el: HTMLElement, value: string) {
el.textContent = value; // never innerHTML
}1.How did the Samy worm bypass MySpace filter that blocked <script>, <body>, and onclick?
2.What was the root cause of the eBay 2014 DOM XSS?
3.What is the key difference between the Samy worm and the Magecart/BA attack?