diff --git a/ios/web/webui/web_ui_messaging_java_script_feature.mm b/ios/web/webui/web_ui_messaging_java_script_feature.mm index 778f20dcb4db3..f4f88936e7205 100644 --- a/ios/web/webui/web_ui_messaging_java_script_feature.mm +++ b/ios/web/webui/web_ui_messaging_java_script_feature.mm @@ -9,6 +9,7 @@ #import "ios/web/public/js_messaging/script_message.h" #import "ios/web/public/web_client.h" #import "ios/web/web_state/web_state_impl.h" +#import "url/scheme_host_port.h" namespace { const char kWebUIMessageHandlerName[] = "WebUIMessage"; @@ -49,6 +50,19 @@ void WebUIMessagingJavaScriptFeature::ScriptMessageReceived( return; } + // `is_main_frame()` and `request_url()` are derived from + // WKScriptMessage.frameInfo, which is constructed from data sent by the + // WebContent process and is therefore attacker-controlled under a + // compromised-renderer threat model. Cross-check against browser-side + // navigation state: the *committed* main-frame URL must be an app-specific + // URL of the same (scheme, host) as the claimed sender. This mirrors the + // hardening in CRWWebUISchemeHandler. + const GURL& committed_url = web_state->GetLastCommittedURL(); + if (!web::GetWebClient()->IsAppSpecificURL(committed_url) || + url::SchemeHostPort(committed_url) != url::SchemeHostPort(url.value())) { + return; + } + if (!script_message.body() || !script_message.body()->is_dict()) { return; }