Released July 29, 2025

CVE-2025-43228

CVSS 4.3 severity medium CWE-451 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N

Overview

Impact:

Description: Visiting a malicious website may lead to address bar spoofing

Researchers: Jaydev Ahire

Key insight. Transient, page-anchored UI (an open picker) was allowed to survive a page-open transition because createNewPage() never dismissed it, letting the displayed UI drift out of sync with the address bar; the fix simply forces picker dismissal before the new page takes over the view.

Patch Details

diff --git a/Source/WebKit/UIProcess/PageClient.h b/Source/WebKit/UIProcess/PageClient.h
index abe91cdbb12d..26060904e694 100644
--- a/Source/WebKit/UIProcess/PageClient.h
+++ b/Source/WebKit/UIProcess/PageClient.h
@@ -331,6 +331,7 @@ public:
         completionHandler(makeUnexpected(WebCore::ExceptionData { WebCore::ExceptionCode::NotSupportedError, "Digital credentials are not supported."_s }));
     }
     virtual void dismissDigitalCredentialsPicker(WTF::CompletionHandler<void(bool)>&& completionHandler) { completionHandler(true); }
+    virtual void dismissAnyOpenPicker() { }
 
     virtual void didChangeContentSize(const WebCore::IntSize&) = 0;
 
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index ca618f862099..c42687a93ccd 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -8678,6 +8678,9 @@ void WebPageProxy::createNewPage(IPC::Connection& connection, WindowFeatures&& w
             return;
         }
 
+        if (RefPtr pageClient = this->pageClient())
+            pageClient->dismissAnyOpenPicker();
+
         newPage->setOpenedByDOM();
 
         if (openerAppInitiatedState)
diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
index 2d3a6d98d1cf..49f14eaaa303 100644
--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.h
@@ -218,6 +218,8 @@ private:
     void dismissDigitalCredentialsPicker(WTF::CompletionHandler<void(bool)>&&) override;
 #endif
 
+    void dismissAnyOpenPicker() override;
+
     void disableDoubleTapGesturesDuringTapIfNecessary(WebKit::TapIdentifier) override;
     void handleSmartMagnificationInformationForPotentialTap(WebKit::TapIdentifier, const WebCore::FloatRect& renderRect, bool fitEntireRect, double viewportMinimumScale, double viewportMaximumScale, bool nodeIsRootLevel, bool nodeIsPluginElement) override;
 
diff --git a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
index 5b54dc660c52..be31742fb9de 100644
--- a/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
+++ b/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
@@ -41,6 +41,7 @@
 #import "NativeWebKeyboardEvent.h"
 #import "NavigationState.h"
 #import "PDFPluginIdentifier.h"
+#import "PickerDismissalReason.h"
 #import "PlatformXRSystem.h"
 #import "RemoteLayerTreeNode.h"
 #import "RunningBoardServicesSPI.h"
@@ -771,6 +772,11 @@ void PageClientImpl::dismissDigitalCredentialsPicker(CompletionHandler<void(bool
 }
 #endif
 
+void PageClientImpl::dismissAnyOpenPicker()
+{
+    [contentView() dismissPickersIfNeededWithReason:WebKit::PickerDismissalReason::ViewRemoved];
+}
+
 void PageClientImpl::showInspectorHighlight(const WebCore::InspectorOverlay::Highlight& highlight)
 {
     [contentView() _showInspectorHighlight:highlight];

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2025-43228
Bugzilla 294374
Component WebKit
Bug Class Bypass
Severity medium
Commit 632a293bf7754147…
Advisory Apple Advisory

Terminology

  • UIProcess — The trusted WebKit2 process that owns the browser UI, window/page lifecycle and the address bar, as opposed to the sandboxed WebContent process that renders untrusted page content.
  • PageClient — An abstraction the UIProcess uses to talk to platform-specific view code; concrete subclasses like PageClientImpl (iOS) bridge to the native content view.
  • createNewPage — The UIProcess path that instantiates and configures a new WebPageProxy in response to content requesting a new window/tab (e.g. window.open).
  • Picker (iOS) — A native input UI (select-option, date, credential, etc.) presented by the WKContentView and anchored over the current page.
  • Address bar spoofing — A UI-integrity bug where the URL shown to the user does not match the content or UI actually being displayed, enabling deception.

Root Cause Analysis

WebPageProxy::createNewPage() runs in the UIProcess when web content asks to open a new window (e.g. window.open() or a target=_blank navigation that the client accepts). On iOS, a picker UI (such as a <select> option picker, date picker, or other content-view presented UI) can be open and visually anchored over the current page’s chrome at the moment a new page is requested. Before this patch, createNewPage() proceeded to configure and swap in the new page (setOpenedByDOM(), origin/app-initiated state, etc.) without first dismissing any picker that the outgoing/current content view still owns. The invariant being violated is that transient, page-anchored UI must be torn down before another page can take over the view, so that no picker outlives the page context that spawned it. Because the picker was left presented across the page transition, an attacker could arrange for the picker (or the spacing/layout it forces) to remain on screen while the address bar and page content were replaced, producing a mismatch between the URL shown and the content/UI actually displayed — i.e. address bar spoofing. The fix adds a call, right before newPage->setOpenedByDOM(), to obtain the current PageClient and invoke dismissAnyOpenPicker(), which on iOS forwards to the content view’s dismissPickersIfNeededWithReason: using PickerDismissalReason::ViewRemoved. A new virtual PageClient::dismissAnyOpenPicker() is introduced with an empty default (no-op on non-iOS ports), and PageClientImpl (iOS) overrides it to actually dismiss pickers. This restores the invariant that opening a new page first dismisses page-anchored picker UI, closing the window during which stale UI could be composited over a page whose URL had changed.

Attack Path

1. Lure victim to malicious page

The victim visits an attacker-controlled origin in Mobile Safari / a WKWebView-based iOS browser.

2. Present a picker

The page triggers a native picker on iOS (e.g. focusing a <select> element or another control whose picker is presented by the content view), so a picker is open and anchored to the current view.

3. Request a new page during picker presentation

While the picker is up, script calls window.open() (or performs a target=_blank navigation) so the UIProcess enters WebPageProxy::createNewPage().

4. Exploit the missing dismissal

Because createNewPage() previously did not dismiss the open picker, the picker (and the display state it forces) persists across the transition to the new page, whose address bar now reflects a different URL.

5. Achieve address bar spoofing

The user sees a trusted-looking or benign URL in the address bar while the on-screen UI/content is controlled by the attacker, enabling phishing or misattribution of displayed content.

Impact Assessment

This is a UI-integrity (spoofing) issue confined to the iOS UIProcess presentation layer, not a memory-safety bug; there is no corruption primitive, no attacker-controlled read/write, and no path to code execution shown by the diff. The realistic impact is deceiving the user about the origin of displayed content, which strengthens phishing and social-engineering attacks but does not by itself compromise the process. Severity is medium precisely because it undermines a security-relevant UI guarantee (the address bar) without granting execution or data-theft primitives on its own.

Audit Directions

Other page-lifecycle transitions in WebPageProxy

Grep WebPageProxy.cpp for setOpenedByDOM, swapToPage, close, and navigation-commit paths and check each transitions dismisses page-anchored UI; look for other places that should call dismissAnyOpenPicker() but do not.

All picker/UI presenters on iOS

In UIProcess/ios search for dismissPickersIfNeeded, present*Picker, and PickerDismissalReason to enumerate every picker type and confirm each has a dismissal tied to view removal / page change, not only the one covered here.

Cross-port parity of the new virtual

Grep for dismissAnyOpenPicker across UIProcess ports (mac, gtk, wpe) to see whether the empty default hides equivalent stale-UI issues (e.g. popup menus, color/date inputs) that also need dismissal on new-page creation.

Spoofing via retained overlays generally

Look for UI elements presented by the content view (context menus, share sheets, form assistants) that are keyed to the old page and audit whether they are torn down on address-bar-changing events; grep for contentView() present/dismiss pairs.

Changed Functions

Function File Change Note
WebPageProxy::createNewPage Source/WebKit/UIProcess/WebPageProxy.cpp modified Adds a call to pageClient()->dismissAnyOpenPicker() immediately before newPage->setOpenedByDOM(), ensuring any open picker is torn down before the new page takes over the view.
PageClient::dismissAnyOpenPicker Source/WebKit/UIProcess/PageClient.h added New virtual method with an empty default implementation, so non-iOS ports are unaffected and only ports that need it override the behavior.
PageClientImpl::dismissAnyOpenPicker Source/WebKit/UIProcess/ios/PageClientImplIOS.mm added iOS override that calls [contentView() dismissPickersIfNeededWithReason:PickerDismissalReason::ViewRemoved] to actually dismiss any presented picker.
PageClientImpl::dismissAnyOpenPicker (declaration) Source/WebKit/UIProcess/ios/PageClientImplIOS.h modified Declares the override of the new PageClient virtual on the iOS PageClientImpl.

Files Changed

Source Files

  • Source/WebKit/UIProcess/PageClient.h
  • Source/WebKit/UIProcess/WebPageProxy.cpp
  • Source/WebKit/UIProcess/ios/PageClientImplIOS.h
  • Source/WebKit/UIProcess/ios/PageClientImplIOS.mm