In October 2005, a hacker named Samy Kamkar discovered a vulnerability on the rapidly growing social network, MySpace. He crafted a JavaScript payload and stored it on his profile. Whenever a user visited his page, the payload executed in their browser, secretly adding Samy as a friend, appending "but most of all, samy is my hero" to their bio, and copying the payload to their profile to infect others.
Within 20 hours, over one million users were infected, forcing MySpace to take down their entire site. Samy had just unleashed the fastest-spreading virus in history.
His weapon of choice? Cross-Site Scripting (XSS).
Year after year, XSS remains one of the most reported vulnerabilities in Bug Bounty programs, paying out millions of dollars annually. In this post, we will demystify XSS, explore its five distinct flavors, and learn how to exploit the trust between a user and a website.
What is XSS? The Core Mechanism
Web browsers render pages by interpreting HTML, CSS, and JavaScript. Under normal circumstances, a website processes user input safely. However, an XSS vulnerability occurs when an application fails to distinguish between legitimate user input and executable code.
If a site does not properly sanitize special characters — like angle brackets (< >), double quotes ("), or single quotes (')—an attacker can inject their own malicious scripts into the web page.
When the victim's browser loads the page, it unknowingly executes the hidden script. Because the script originates from the trusted website, it inherits the site's permissions, bypassing the Same-Origin Policy (SOP). This grants the attacker a backstage pass to steal session cookies, hijack accounts, or dynamically alter the page's content.
The 5 Flavors of Cross-Site Scripting
Not all XSS vulnerabilities are created equal. Depending on how the payload is delivered and executed, XSS is categorized into five types:
1. Reflected XSS (The Bait and Switch)
In Reflected XSS, the malicious payload is delivered and executed via a single HTTP request. It is not stored in the application's database. Instead, the application takes the input (often from the URL) and immediately "reflects" it back to the user in the HTTP response.
- The Attack: An attacker crafts a malicious link, such as
https://target.com/search?q=<script>alert(document.domain)</script>, and tricks the victim into clicking it. - The Impact: Because the payload requires social engineering (phishing) to execute, it is highly targeted but easily blocked by modern browsers if not executed correctly.
2. Stored XSS (The Persistent Poison)
Stored XSS is significantly more dangerous. Here, the application accepts the malicious payload and permanently saves it on the server (e.g., in a database, forum comment, or user profile).
- The Attack: The attacker leaves a comment containing a malicious script.
- The Impact: Every single user who views that comment will have their browser execute the script. No phishing links are required; the trap is set on the legitimate site, leading to mass compromises.
3. DOM-Based XSS (The Invisible Puppeteer)
Unlike Reflected and Stored XSS, where the server reflects the payload in the HTTP response, DOM-based XSS occurs entirely within the victim's browser. The server's response might not contain the malicious script at all.
- The Mechanism: It happens when an application's legitimate JavaScript takes input from an attacker-controllable Source (like
location.hashor the URL fragment#) and passes it to an unsafe execution Sink (likedocument.write,eval(), orinnerHTML). - The Impact: Because the payload (e.g., after the
#symbol in a URL) is often not sent to the server, server-side firewalls and logs are completely blind to the attack.
4. Blind XSS (The Time Bomb)
Blind XSS is a stealthy variant of Stored XSS. The payload is saved by the server but is executed in a completely different, restricted part of the application that the attacker cannot see — usually an internal administrative panel.
- The Tool of Choice: Hackers use tools like XSSHunter to hunt for these. You inject payloads into contact forms, support tickets, or username fields. Weeks later, when a customer support agent or system administrator reviews the ticket, the payload fires, silently emailing you a screenshot of the admin panel, the DOM, and the admin's session cookies.
5. Self-XSS (The Self-Inflicted Wound)
Self-XSS occurs when the payload only executes for the user who inputted it. For example, a vulnerability in a private settings page protected by CSRF tokens. By itself, Self-XSS is practically useless and usually rejected by Bug Bounty programs because you are only hacking yourself. However, elite hackers find ways to chain Self-XSS with other vulnerabilities, like Login CSRF, to force a victim to log into the attacker's account and trigger the payload.
Proving Impact: Beyond alert(1)
Beginners often use <script>alert(1)</script> to test for XSS. While seeing a pop-up box is exciting, it does not prove the true impact to a security engineer.
A better proof of concept is alert(document.domain). This confirms exactly which domain the script is executing under, proving you have bypassed the Same-Origin Policy (SOP). If you can alert the domain, you can read it. If you can read it, you can manipulate it.
If a site protects its session cookies using the HttpOnly flag, you won't be able to steal the cookies via document.cookie. However, XSS still allows you to:
- Scrape the DOM: Read CSRF tokens from the page to forge state-changing requests (like changing passwords or transferring money) on the user's behalf.
- Keylogging: Inject a script that records every keystroke the victim makes on the compromised page.
- Virtual Defacement & Phishing: Dynamically rewrite the HTML to display a fake login prompt, tricking the user into handing over their credentials directly.
Bypassing Filters: The Hacker's Mindset
Developers use Web Application Firewalls (WAFs) and filters to block <script> tags. When this happens, you must get creative:
- Event Handlers: Use HTML attributes that execute JavaScript when an event occurs. For example:
<img src=x onerror=alert(document.domain)>. If the image fails to load, the script fires. - Alternative Encodings: Browsers are incredibly forgiving. You can encode payloads using URL encoding, HTML entities, or even Unicode to slip past defensive filters.
- Context Matters: If your input is being reflected inside an existing JavaScript variable (e.g.,
var name = "YOUR_INPUT";), you don't need HTML tags. You just need to break out of the string using quotes and inject your code:"; alert(document.domain); //.
In the next post, we will continue exploring Client-Side Attacks, diving into the world of Cross-Site Request Forgery (CSRF), CORS misconfigurations, and Open Redirects. We will learn how to force a victim's browser to execute actions without their consent.