Secure Steps

Cybersecurity assessments, insights, and clear remediation.

Category:

perchance — Browser Extension Origin and Context Confusion | NNS CTF

perchance — Browser Extension Origin and Context Confusion

Platform: NNS CTF 2026
Category: Web / Browser Extension

1. Components

The challenge combines a bot, a browser extension, an attacker-controlled page, and the trusted Rust documentation origin. The goal is to make code execute in the Rust documentation origin, where a non-HttpOnly flag cookie is available.

The exploit is not one isolated bug. It is a chain of trust failures: weak URL validation, an unvalidated cross-origin message, an attacker-observable imported function, and unsafe HTML insertion.

2. Weak URL validation

The options page accepts a URL only if it contains https://doc.rust-lang.org/. This is a substring check, not an origin check. A URL using userinfo can satisfy the check while loading an attacker host, for example:

https://anything@ATTACKER/?https://doc.rust-lang.org/

The browser interprets ATTACKER as the real host; the Rust string appears only in the userinfo/query portion. The code then stores u.origin as activateOn, allowing the attacker origin to become the extension’s activation origin.

3. Cross-origin postMessage bug

The options script listens for messages and calls updateConfig whenever the data begins with http. It never checks event.origin or that event.source is the expected window:

window.addEventListener('message', function (e) {
  if (e.data.match(/^https?/)) updateConfig(e.data);
});

An attacker page can frame the options page and post the crafted URL. This bypasses the need for a manual user entry and sets the extension configuration from an untrusted origin.

4. Replacing the sanitizer function

On an activated page, the content script creates a random name and injects a module import into the page context:

const nonce = 'a' + crypto.randomUUID().replaceAll('-', '');
scr.textContent = `import ${nonce} from 'http://localhost:3000/jsxss.js';
window['${nonce}']=${nonce}`;

The script waits until window.wrappedJSObject[nonce] exists and then calls it on the current URL. The random name is not secret from page observers: a MutationObserver can watch newly inserted script elements, extract the nonce, and define the corresponding global before the import completes. The attacker-controlled replacement returns HTML instead of sanitized text.

5. Reaching the trusted origin

The content script stores the return value as previous. On the final visit it creates a paragraph and assigns:

elm.innerHTML = `Previous: ${prev}`;

Because the stored value can contain markup, an image payload such as an onerror handler executes when inserted. The final navigation is to https://doc.rust-lang.org/stable/std/, so the payload runs where the flag cookie is readable and sends it to the attacker-controlled endpoint.

6. Exploit sequence

  1. Frame the extension options page.
  2. Send an attacker URL containing the required Rust substring to set activateOn.
  3. Reload the attacker page so the content script executes.
  4. Observe the imported script, recover its random global name, and replace the sanitizer function.
  5. Return an HTML payload that exfiltrates document.cookie.
  6. Reset the activation origin to Rust documentation.
  7. Navigate to the Rust standard-library page and read the exfiltrated cookie.

7. Verified result

The bot requested an /exfil URL containing the URL-encoded flag cookie. Decoding that value produced:

flag=NNS{p3RH4ps_y0u_M16h7_P0551b1Y_3Nj0Y_c7Fs_P3RCH4nc3}

8. Defensive lessons

  • Compare parsed URL origins, not substrings.
  • Validate both event.origin and event.source for postMessage.
  • Do not expose security-sensitive functions through page globals.
  • Keep extension data isolated from page scripts.
  • Use text insertion or a trusted sanitizer instead of innerHTML.
  • Mark sensitive cookies HttpOnly and Secure where possible.
en_US
Secure Steps
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.