Skip to content

Validate message event source and origin in receive()#85

Open
thistehneisen wants to merge 1 commit into
web-eid:mainfrom
thistehneisen:fix/validate-message-event-sender
Open

Validate message event source and origin in receive()#85
thistehneisen wants to merge 1 commit into
web-eid:mainfrom
thistehneisen:fix/validate-message-event-sender

Conversation

@thistehneisen

Copy link
Copy Markdown

Fixes #84.

Problem

WebExtensionService.receive() registered a window message listener that
accepted any message whose data.action was a string starting with
web-eid:, without validating event.source or event.origin. Any context
able to deliver a message event to the page — an embedded iframe (ad, widget,
user-rendered content), a malicious framing parent, or another window — could
forge web-eid:*-success / *-failure / *-ack responses and drive the
resolution of the library's pending authenticate(), sign(),
getSigningCertificate() and status() promises with attacker-controlled data
(CWE-346 / CWE-940).

This is inconsistent with the extension content script in
web-eid-webextension, which already enforces event.source === window before
relaying messages.

Fix

Reject messages whose event.source is not the page's own window or whose
event.origin is not the page's own origin. Legitimate responses are posted by
the content script into the same window, so both checks pass for real traffic.

if (event.source !== window) return;
if (event.origin !== window.location.origin) return;

Tests

  • Existing tests dispatched responses via window.postMessage, which in jsdom
    sets source to null and origin to "". They now dispatch a
    MessageEvent with source: window / origin: window.location.origin, as
    the content script does.
  • Added WebExtensionService-sender-validation-test.ts: foreign-source and
    foreign-origin success/failure responses are ignored (request times out
    instead of resolving with forged data), while a legitimate same-window
    response still resolves.

Full suite (56 tests), eslint, and tsc --noEmit pass.

Note

This is not a server-side authentication bypass — the token's certificate and
signature are verified server-side and the origin is bound by the trusted
extension/native app. The impact is client-side: flow hijack/abort, response
pre-emption, and spoofing of any client-side trust placed in a response before
server verification. Additional defense-in-depth hardening (a per-request
correlation nonce, warnings input validation, and an allow-list in
deserializeError) is described in #84 and left out of this change to keep it
focused.

The window "message" listener accepted any message whose data.action
started with "web-eid:" without checking event.source or event.origin.
This allowed an embedded iframe, a framing parent, or any other window
to forge extension responses and resolve/reject the library's pending
authenticate(), sign(), getSigningCertificate() and status() promises
with attacker-controlled data (CWE-346 / CWE-940).

Reject messages whose source is not the page's own window or whose
origin is not the page's own origin, matching how the extension content
script posts responses back into the same window. Tests are updated to
dispatch responses as the content script would, plus regression tests
covering foreign-source and foreign-origin responses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing sender validation in message receiver allows forged extension responses (CWE-346)

1 participant