Glossary · Security
Cross-site scripting
Also called: XSS, cross site scripting, stored XSS, reflected XSS
Definition
XSS is a vulnerability where untrusted input is rendered as executable script in another user's browser, letting an attacker act as that user.
Cross-site scripting (XSS) happens when an application takes input from one user and puts it into a page without proper encoding, so the browser treats it as HTML or JavaScript instead of text. The script then runs with the victim’s session on the vulnerable site: it can read page data, make requests as the user or change what they see. In the OWASP Top 10 2021 it is grouped under A03 Injection.
The three types
- Stored. The payload is saved (a comment, a profile name, a support ticket) and served to everyone who views it. Usually the most serious.
- Reflected. The payload is in the request, often a URL parameter such as a search term, and echoed straight back in the response.
- DOM-based. Client-side JavaScript reads attacker-controlled data (for example
location.hash) and writes it into the page with an unsafe sink such asinnerHTML.
What a tester checks
Put a harmless marker into every field that is later displayed (to you, to other users, to admins in a back office) and see whether it comes back as text or as live markup. An automated check in Playwright, for authorised testing only:
import { test, expect } from '@playwright/test';
test('comment body is rendered as text, not HTML', async ({ page }) => {
const probe = '<img src=x onerror="window.__xssProbe=1">';
await page.goto('/posts/1');
await page.getByLabel('Comment').fill(probe);
await page.getByRole('button', { name: 'Post comment' }).click();
// Escaped output shows the literal characters on screen
await expect(page.getByText(probe)).toBeVisible();
// and the handler never ran
expect(await page.evaluate(() => (window as any).__xssProbe)).toBeUndefined();
});
Also check output contexts beyond plain HTML: values inside attributes, inside inline scripts, in URLs (javascript: links) and in rich-text editors, because each needs different encoding.
Defences to verify
- Output encoding by default. Modern frameworks (React, Angular, Vue) escape text automatically; look for the escape hatches such as
dangerouslySetInnerHTML,v-htmlorinnerHTML. - HTML sanitisation for features that genuinely accept markup.
- A Content Security Policy header that blocks inline scripts, as a second layer.
HttpOnlysession cookies, so a successful XSS cannot simply read the session cookie.
Never test on systems you do not own or lack written permission to test.