← All issues

[1] WebAuthn UIProcess origin spoofing via unvalidated IPC fields

WebAuthn's entire pitch is phishing resistance — yet the UIProcess took the renderer's word for which origin was actually asking.

Severity: High | Component: WebKit UIProcess WebAuthentication | 315ac30

Rated High because the diff removes a missing-validation gap at the WebContent-to-UIProcess boundary that let a compromised renderer bind a WebAuthn ceremony to an arbitrary relying-party origin; escalation to cross-site credential impersonation requires only a prior renderer compromise, which is the assumed threat model, though the bug yields no memory-corruption primitive.

A compromised WebContent process could spoof the securityOrigin in FrameInfoData or the parentOrigin parameter when sending WebAuthn MakeCredential/GetAssertion IPC messages to the UI process. This would let an attacker page impersonate a different origin (e.g. a bank) for credential creation or assertion. This patch adds MESSAGE_CHECKs to prevent that.

Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.cpp

-void WebAuthenticatorCoordinatorProxy::getAssertion(FrameIdentifier frameId, FrameInfoData&& frameInfo, ... std::optional<WebCore::SecurityOriginData> parentOrigin, RequestCompletionHandler&& handler)
+void WebAuthenticatorCoordinatorProxy::getAssertion(IPC::Connection& connection, FrameIdentifier frameId, FrameInfoData&& frameInfo, ... std::optional<WebCore::SecurityOriginData> parentOrigin, RequestCompletionHandler&& handler)
{
RefPtr webPageProxy = m_webPageProxy.get();
...
+ RefPtr frame = WebFrameProxy::webFrame(frameId);
+ if (!frame) {
+ RELEASE_LOG_ERROR(WebAuthn, "Frame not found for WebAuthn GetAssertion request");
+ return handler({ }, static_cast<AuthenticatorAttachment>(0), ExceptionData { ExceptionCode::InvalidStateError });
+ }
+ if (frame->url().protocolIsInHTTPFamily()) {
+ auto expectedOrigin = SecurityOriginData::fromURLWithoutStrictOpaqueness(frame->url());
+ MESSAGE_CHECK_COMPLETION_BASE(frameInfo.securityOrigin == expectedOrigin, connection,
+ handler({ }, static_cast<AuthenticatorAttachment>(0), ExceptionData { ExceptionCode::InvalidStateError }));
+ }
+ if (parentOrigin) {
+ bool foundMatchingAncestor = false;
+ bool hasHTTPAncestor = false;
+ for (RefPtr ancestor = frame->parentFrame(); ancestor; ancestor = ancestor->parentFrame()) {
+ if (!ancestor->url().protocolIsInHTTPFamily())
+ continue;
+ hasHTTPAncestor = true;
+ auto ancestorOrigin = SecurityOriginData::fromURLWithoutStrictOpaqueness(ancestor->url());
+ if (*parentOrigin == ancestorOrigin) { foundMatchingAncestor = true; break; }
+ }
+ if (hasHTTPAncestor)
+ MESSAGE_CHECK_COMPLETION_BASE(foundMatchingAncestor, connection, handler(...));
+ }
handleRequest({ ..., WTF::move(frameInfo), ..., parentOrigin }, WTF::move(handler));
}

LayoutTests/http/tests/ipc/web-authenticator-get-assertion-spoofed-origin-crash.html

+const spoofedOrigin = { data: { variantType: 'WebCore::SecurityOriginData::Tuple', variant: { protocol: 'https', host: 'evil.com', port: {} } } };
+CoreIPC.UI.WebAuthenticatorCoordinatorProxy.GetAssertion(IPC.webPageProxyID, { frameInfo: { ... securityOrigin: spoofedOrigin, topOrigin: spoofedOrigin, ... }, options: { ... }, mediation: 0, parentOrigin: {} });

Both makeCredential and getAssertion now take an IPC::Connection& as their first parameter so the receivers can issue connection-terminating checks. Each handler looks up the real frame via WebFrameProxy::webFrame(frameId), bails with InvalidStateError if none is found, and — when the frame URL is HTTP(S) — recomputes the expected origin with SecurityOriginData::fromURLWithoutStrictOpaqueness(frame->url()) and asserts frameInfo.securityOrigin == expectedOrigin via MESSAGE_CHECK_COMPLETION_BASE. getAssertion additionally walks frame->parentFrame() ancestors and, if any HTTP(S) ancestor exists, requires parentOrigin to equal one of those ancestor origins. Two IPC-testing layout tests send spoofed-origin requests and expect the WebContent process to be terminated.

Trusting an IPC-supplied security origin from the untrusted WebContent process without re-validating it against UI-process-owned frame state.

WebKit splits work between a sandboxed WebContent (renderer) process and a privileged UI process; security-sensitive operations cross this boundary as IPC messages, and the UI process must treat every field as attacker-controlled. The MESSAGE_CHECK/MESSAGE_CHECK_COMPLETION_BASE macros terminate the offending IPC connection — killing the WebContent process — when their predicate fails, which is why the regression tests are named *-crash. FrameInfoData.securityOrigin is the renderer's claim about a frame's origin; WebFrameProxy::webFrame(frameId) returns the UI process's own record of that frame, whose url() is authoritative. WebAuthn MakeCredential/GetAssertion perform credential ceremonies scoped to a relying-party origin, and for cross-origin (iframe) assertions a parentOrigin describes the embedder, which must match an actual ancestor frame's origin.

This is an origin-spoofing / authority-confusion bug, not memory corruption. Before the fix, the UI-process WebAuthn receivers consumed frameInfo.securityOrigin (and the parentOrigin argument) directly from the IPC message and propagated them into handleRequest/buildClientDataJson without checking them against the authoritative frame state. In WebKit's IPC trust model the renderer is untrusted: any field it serializes must be re-validated against UI-process-owned ground truth, and here that step was simply absent.

A compromised WebContent process crafts a MakeCredential/GetAssertion message whose FrameInfoData.securityOrigin claims an arbitrary origin (e.g. https://bank.com) while the real frame is hosted elsewhere. Pre-fix, the UI process believed that claim, so the WebAuthn ceremony — including the clientDataJSON origin field and the relying-party scoping — would be performed under the spoofed origin. The cross-origin variant additionally spoofs parentOrigin to claim an arbitrary embedder. This is exploitable as a WebAuthn origin-spoofing bypass by a renderer that can already send IPC; it is not a memory-corruption primitive and requires prior renderer compromise. The downstream propagation of the spoofed origin into buildClientDataJson's origin field is consistent with the provided source, though the full effect on the authenticator ceremony is inferred from how handleRequest consumes the data.

This vulnerability weakens the WebContent-to-UIProcess trust boundary as it applies to WebAuthn origin binding. WebAuthn's entire value proposition — phishing resistance — rests on the relying-party origin presented to the authenticator being the real origin of the requesting frame. An attacker who has compromised the renderer could request creation or assertion of credentials under an arbitrary origin such as a bank, impersonating that site to the platform authenticator. The fix re-derives the origin from UI-process-owned WebFrameProxy state and MESSAGE_CHECKs against the renderer's claim, mirroring how clipboard, media, and getUserMedia receivers already validate caller origins.