The Ravens’ profile page reads the part after the # in the URL and drops it into the alt attribute of the avatar image. All of this happens in the page’s own JavaScript, so the server never sees it. The Hacker added protection that removes <script> from the value.
The Ravens forum profile page reads what is after the # in the address, in the page JavaScript, and puts it in an avatar alt via innerHTML — the server sees nothing. The Hacker added protection that removes script. You type the fragment in the field, or the whole address in the address bar.
Craft a URL fragment (after the #) that makes the victim’s browser run code calling steal(). Note: no request is sent to the server.
The fragment produces HTML that runs code calling steal inside the page, with no request to the server.
Why the solution worked
This is DOM-based XSS: the source is attacker-controlled data (the URL fragment after #) and the sink is innerHTML in the page’s JavaScript, not the server. The problem is twofold: the value lands inside an attribute, so you must break out of it first, and the blacklist is bypassable. The fix: do not build HTML from strings — use textContent or setAttribute, which always treat the value as data; and if you must produce HTML, sanitize with a whitelist and respect the context the value goes into.
The root cause
The source is attacker-controlled data (the address fragment) and the sink is innerHTML in client JavaScript; the value lands inside an attribute so it needs a break-out, and the protection is a blacklist. The whole bug is in the client, not the server.
How the code is fixed
In DOM vulnerabilities the server is often fine and the whole bug is in client JavaScript. Watch the sources (location, document.referrer, messages) and the sinks (innerHTML, document.write, eval).
Vulnerable code
const value = decodeURIComponent(location.hash.slice(1));
const safe = value.replace(/<script>/gi, ''); // blacklist
// Value is built into HTML inside an attribute: it can break out.
avatar.innerHTML = '<img alt="' + safe + '" class="avatar">';
Safe code
const value = decodeURIComponent(location.hash.slice(1));
// Treat it as data, never as markup: setAttribute can never break out.
const img = document.createElement('img');
img.className = 'avatar';
img.alt = value;
avatar.replaceChildren(img);
Impact in the real world
DOM bugs slip past server reviews because the server is entirely clean; they hit users of modern single-page apps and steal sessions and tokens straight from the browser.
How defenders detect it
Defenders grep client code for sources (location, document.referrer, messages) and sinks (innerHTML, document.write, eval) and replace them with textContent and setAttribute.