Written by Ruben Selby, founder and penetration tester at Wolfcore. Last reviewed 9 November 2026.
In one sentence: Cross-site scripting happens when a website shows text from a user as if it were part of the page’s own code, so a visitor’s browser runs something the site owner never wrote.
This page explains the mechanism and the developer fix. For the plain definition and business impact, see our entry on cross-site scripting.
What it is
Your browser trusts a website’s own scripts. It gives them access to the visitor’s session on that site. XSS abuses this trust. OWASP puts it this way: the browser has no way to know the script should not be trusted, so it runs it, and the script can reach cookies, session tokens or other sensitive information the browser holds for that site. Scripts can also rewrite the page.
OWASP notes the name is a misnomer. It began with attacks that stole data across sites, but now covers injecting almost any content.
How it works
The root cause is always the same: untrusted data goes into a page without the right protection. OWASP defines untrusted data as anything your application did not create itself, such as form input, URL parameters, stored user data and responses from other services.
There are three routes to a victim.
- Reflected. The data comes in with the request and comes straight back in the response, for example in a search result or error message. The attacker has to get the victim to open a crafted link, often by email or another website. This is why XSS and phishing often travel together.
- Stored. The data is saved, for instance in a comment, profile or support ticket, and shown later to everyone who views it. OWASP says the best place for an attacker to put it is somewhere many users, or users with higher privileges, will see it.
- DOM-based. The page’s own JavaScript takes data from somewhere such as the URL and writes it into the page unsafely, all in the browser. The server may never see it.
Once the script runs, what the attacker is after is usually the victim’s logged-in session, or to change what the victim sees, or to act as the victim. OWASP says the most severe attacks involve disclosure of the session cookie, allowing session hijack and account takeover.
A harmless illustration of the pattern
The vulnerable pattern builds HTML by joining strings. The fixed pattern treats the data as text.
// Vulnerable: user text is parsed as HTML
box.innerHTML = "Hello " + userName;
// Fixed: user text is only ever displayed as text
box.textContent = "Hello " + userName;
OWASP’s prevention cheat sheet gives the same advice. It talks about “sinks”, the places where data is written into a page. It says to refactor away from unsafe sinks like innerHTML and use textContent or value instead.
Who it affects and how badly
- Sole trader to 10 staff (trades, retail, hospitality). A WordPress or shop plugin with a stored XSS flaw can be used against site admins. Worst case: the admin session is taken and the site is altered.
- 10 to 50 and 50 to 250 (SaaS start-ups, recruitment, estate agents). Customer portals and CRM tools show user-entered text to staff. Worst case: a customer record or support ticket runs code in a staff member’s browser.
- Regulated (fintech, healthcare, councils). Account takeover through a stored flaw can expose personal data. That may be a breach to report to the ICO within 72 hours, or to an EU supervisory authority, plus loss of client trust.
What we check on a test
- Every place the application takes input and shows it again: search boxes, forms, comments, profile fields, error pages and URL parameters.
- Whether the output is encoded for the place it lands: HTML body, attribute, JavaScript, URL or CSS.
- Client-side code that writes into the page, to find unsafe sinks such as
innerHTML. - Admin and support screens, where one user’s data is shown to another user.
- Cookie flags, such as
HttpOnly, and whether a Content Security Policy is present and sensible. - File names, headers and data from other systems that reach the page.
The UK angle
Cyber Essentials will not test your custom web application. The NCSC’s requirements (version 3.3) say bespoke and custom components of web applications are out of scope, and that the best way to mitigate vulnerabilities in applications is robust development and testing in line with commercial best practice, pointing to the Software Security Code of Practice. The NCSC’s vulnerability scanning guidance adds that web application scanners look for issues such as injection from malicious user input, and that automated scanning cannot compare to manual penetration testing for breadth and depth. A certificate does not mean your own web app is free of XSS.
Diagram: stored XSS path
Worked example (illustration only)
A 15-person recruitment agency has a customer portal where clients leave notes on candidates. The notes field shows whatever is typed, without encoding. A hostile user saves a note that contains code. When a consultant opens that candidate, their browser runs it in the portal’s context. The risk is that the consultant’s session is exposed. The cost could be a data breach involving candidate CVs, a report to the ICO, and clients who stop trusting the portal. The fix is output encoding on the notes field, plus a review of every other field that is shown back.
How to detect it
- Look in web server and application logs for requests with markup characters in parameters, such as angle brackets, in places where text is expected.
- Review saved content in your database for tags that should not be there.
- Watch for browser console reports of Content Security Policy violations, if you have a policy in report mode.
- Run the free scan against your own site. OWASP says tools such as Nessus and Nikto can help scan for these flaws but only scratch the surface.
- Ask for a manual test on any app that handles logins, payments or personal data.
How to prevent it
- Use a modern framework that escapes output by default, and learn where it does not (for example React’s
dangerouslySetInnerHTML, or Angular’sbypassSecurityTrustfunctions). OWASP lists these as escape hatches. - Use safe text sinks such as
textContentinstead ofinnerHTML. - Apply output encoding that fits the context: HTML body, attribute, JavaScript, URL or CSS. OWASP says the wrong encoding can introduce weaknesses.
- If users must submit HTML, sanitise it with a maintained library such as DOMPurify, and keep it patched.
- Never put untrusted data directly into script blocks, event handler code or CSS source.
- Add a Content Security Policy as a second layer, not the first. OWASP says it is easy to get wrong and should not be your primary defence.
- Set the
HttpOnlyflag on session cookies to limit the damage. OWASP says cookie attributes limit impact but do not fix the root cause. - Do not rely on a web application firewall or input filter alone. OWASP says they cannot reliably stop all XSS.
- Turn off HTTP TRACE on web servers.
FAQ
What are the three types of XSS? Reflected, stored and DOM-based. They differ in how the data reaches the victim.
Is a firewall enough to stop XSS? No. OWASP says web application firewalls are an extra layer and cannot reliably prevent all XSS or fix vulnerable code.
Does Cyber Essentials cover XSS in my own website? No for bespoke web application components. The NCSC says those are out of scope.
Related terms
cross-site-scripting, owasp-top-10, content-security-policy, session-hijacking, sql-injection, cyber-essentials, how-sql-injection-works
More in the hacking techniques hub.
How Wolfcore helps
A Wolfcore penetration test finds this the way an attacker would, by hand, not with a scanner. See our penetration testing service and web application penetration testing, and start with the free web vulnerability scan.