Blog · E.26 · Technical

Cross-site scripting (XSS) explained

If SQL injection attacks your database, XSS attacks your users. When an app reflects attacker-controlled input into a page without encoding it, that input runs as JavaScript in the victim's browser - acting as them, stealing their session, taking over their account. It's one of the most common web flaws, and on a logged-in app it's rarely cosmetic. Here's how XSS works, the three types (stored, reflected, DOM), what it yields, how to test, and how to shut it down.

XSSStored / Reflected / DOMSession TheftOutput EncodingCSP
XSS: Attacker Script in Victim's Browser · Stored / Reflected / DOM · Session Theft & Account Takeover · Fix = Context-aware Output Encoding + CSP + HttpOnly XSS: Attacker Script in Victim's Browser · Stored / Reflected / DOM · Session Theft & Account Takeover · Fix = Context-aware Output Encoding + CSP + HttpOnly
// TL;DR

Cross-site scripting (XSS) lets an attacker run JavaScript in another user's browser by getting the app to include untrusted input in a page without safe encoding. Because the script runs as the victim on the vulnerable site, it can steal session tokens (account takeover), exfiltrate data, act as the user, and capture credentials. Three types: stored (saved server-side, hits every viewer - highest impact), reflected (in a request, via a crafted link), DOM-based (entirely client-side, unsafe JS writing input into the page). The fix: context-aware output encoding (HTML/attribute/JS/URL) so input renders as text, plus auto-escaping frameworks, safe DOM APIs, a Content Security Policy, and HttpOnly cookies. Filtering isn't enough. Detail below; part of every web app test.

// 01 How XSS works

Web pages mix content with executable JavaScript, and the browser runs whatever script it finds in a page it trusts. XSS exploits that: it occurs when an application includes untrusted input in the pages it sends without safely encoding it, so the input is interpreted as script rather than displayed as text. If a site echoes your name into a page and you supply <script>...</script> instead of a name, the browser executes it. The critical detail is whose browser and what context: the malicious script runs in the victim's browser, in the security context of the vulnerable site - so it inherits the victim's session, cookies and access. That's what makes XSS an attack on your users rather than your server: the attacker borrows each victim's identity on your site. Same root cause as SQL injection - untrusted data crossing into a code context - but the context here is the browser.

// 02 The three types

01

Stored XSS

The script is saved on the server (a comment, profile field, message) and served to every user who views it - persistent, high-impact.

02

Reflected XSS

The script is in a request (e.g. a URL parameter) and reflected in the immediate response - hits a user tricked into a crafted link.

03

DOM-based XSS

Happens entirely in the browser: client-side JS takes attacker input and writes it into the page unsafely, server not necessarily involved.

04

All three

…end the same way: attacker script executing in a victim's browser on your site.

Stored is the most dangerous because it's persistent and hits everyone - one poisoned comment attacks every reader, including admins. DOM-based is the sneakiest, living purely in client-side code where server-side defences don't reach - and it's where prototype pollution and other client bugs often surface.

// 03 What an attacker gains

Because the injected script runs as the victim on your site, XSS is far from cosmetic. An attacker can: steal session cookies or tokens and hijack the session - straight to account takeover; read and exfiltrate whatever data the user can see; perform actions as the user (change account details, submit requests, transfer value); capture keystrokes and credentials by injecting a fake login form; and deface or manipulate the page to phish. On an authenticated application handling sensitive data, that's a serious vulnerability - and stored XSS affecting many users, or XSS chained with a CORS or session flaw, can be critical. The severity always tracks the context: reflected XSS on a marketing page is low; stored XSS in an admin panel is a breach.

// 04 How to test for it

Testing probes every place user input is reflected into a page - form fields, URL parameters, headers, and any value the app displays back. The tester injects context-appropriate payloads (different for HTML body, attributes, JavaScript and URLs) and checks whether the input is rendered as text or executed as script. For stored XSS, they submit a payload and check whether it fires when the content is later viewed - by another user, or in an admin view. For DOM-based, they trace client-side JavaScript from input sources to dangerous sinks (like innerHTML), which requires reading the front-end code, not just fuzzing forms. Confirming impact - e.g. capturing a session token - is done within the rules of engagement. This context-and-code-aware work is why a real manual test finds the XSS (especially DOM-based) that scanners miss or misclassify.

// 05 How to prevent XSS

The core control is context-aware output encoding: whenever user-controlled data is placed into a page, encode it for where it appears - HTML body, attribute, JavaScript, or URL - so it's rendered as text and never executed as script. Get the context right, because encoding safe for one place is unsafe in another. In practice, modern frameworks that auto-escape output by default (React, Angular, etc.) provide strong protection - as long as you don't bypass their safe defaults (e.g. dangerouslySetInnerHTML). For DOM-based XSS, use safe DOM APIs that set text, not HTML, and never pass untrusted input to dangerous sinks. Add defence in depth: a well-configured Content Security Policy restricts what scripts can run (blunting XSS even if one slips through), and the HttpOnly flag on session cookies stops script from reading them - limiting the session-theft impact. Input validation helps at the margins, but encoding at output is the control that actually prevents XSS. It's tested in every web app engagement, per our methodology.

// 06 Frequently asked questions

What is cross-site scripting?

A vulnerability that lets an attacker inject malicious JavaScript into a web page that then runs in another user's browser. It occurs when an app includes untrusted input in its pages without safe encoding, so the input is interpreted as script. Because it runs in the victim's browser in the site's context, it can act as that user - reading session tokens and data, performing actions, and manipulating what they see. On authenticated apps it frequently leads to account takeover.

What are the three types of XSS?

Stored (script saved on the server - e.g. a comment or profile field - and served to every viewer, persistent and high-impact), reflected (script in a request like a URL parameter, reflected in the immediate response, hitting a user tricked into a crafted link), and DOM-based (entirely in the browser, where client-side JS writes attacker input into the page unsafely). All three result in attacker script running in a victim's browser.

What can an attacker do with XSS?

Steal session cookies or tokens and hijack the session (account takeover); read and exfiltrate data the user can see; perform actions as the user (change details, make requests); capture keystrokes and credentials via injected fake forms; and deface or manipulate the page. On authenticated apps with sensitive data it's serious, and stored XSS affecting many users, or XSS chained with other flaws, can be critical.

How do you prevent XSS?

Context-aware output encoding: encode user data for where it appears (HTML body, attribute, JS, URL) so it renders as text. Auto-escaping frameworks give strong protection if their safe defaults aren't bypassed. For DOM-based XSS, use safe DOM APIs that set text not HTML. A well-configured Content Security Policy adds defence in depth, and HttpOnly cookies limit cookie theft. Input validation helps, but output encoding is the control that prevents XSS.

// 07 Related reading

UG

Usama Gul

Founder & Penetration Testing Lead, CyberFortify

Finds XSS in every context — stored, reflected, and the DOM-based cases that require reading the front-end code — and proves the session-theft impact that turns “pop-up” into account takeover.

Reflecting user input to the page?

XSS attacks your users — stealing sessions and taking over accounts. We test every context, including the DOM-based cases in your front-end code, and hand you the encoding-and-CSP fix.

Scope a web app test → Web app testing →