← All issues

[10] XMLSerializer Extension URL Leak

Severity: Medium | Component: WebCore XML serialization | 0f4832c

Rated Medium because the observable effect is trivial extension fingerprinting from any web page via a single JavaScript call, but the impact is limited to information disclosure (extension identity) without memory corruption or code execution.

Fix the bug by masking out custom scheme URLs in XMLSerializer.

Source/WebCore/xml/XMLSerializer.cpp

String XMLSerializer::serializeToString(Node& node)
{
- return serializeFragment(node, SerializedNodes::SubtreeIncludingNode, nullptr, ResolveURLs::No, SerializationSyntax::XML);
+ return serializeFragment(node, SerializedNodes::SubtreeIncludingNode, nullptr, ResolveURLs::NoExcludingURLsForPrivacy, SerializationSyntax::XML);
}

Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewConfiguration.mm

+ NSString *xmlSource = [webView stringByEvaluatingJavaScript:@"(new XMLSerializer()).serializeToString(document.body)"];
+ EXPECT_WK_STREQ(xmlSource, @"<body xmlns=\"http://www.w3.org/1999/xhtml\"><iframe src=\"webkit-masked-url://hidden/\"></iframe><iframe src=\"http://apple.com/baz.html\"></iframe></body>");

The change is minimal: XMLSerializer::serializeToString now passes ResolveURLs::NoExcludingURLsForPrivacy instead of ResolveURLs::No when calling serializeFragment. This causes custom scheme URLs (used by web extensions) to be replaced with webkit-masked-url://hidden/ in the serialized output. The test confirms that XMLSerializer output now masks extension URLs while preserving normal http:// URLs.

WebKit supports masked URL schemes as a privacy mechanism: when a web extension injects content using a custom URL scheme (e.g., another-scheme://foo.com/bar.js), the browser can be configured to replace these URLs with webkit-masked-url://hidden/ during DOM serialization. This prevents web pages from discovering which extensions are installed — a common fingerprinting vector. The ResolveURLs enum controls this behavior: ResolveURLs::No serializes URLs verbatim, while ResolveURLs::NoExcludingURLsForPrivacy preserves relative URLs but masks private custom scheme URLs. The innerHTML serialization path already used the privacy-aware variant.

The root cause is a bypass-via-alternative-path: a privacy mechanism was applied to innerHTML serialization but not to XMLSerializer, which ultimately calls the same serializeFragment function with a different enum value. Any web page could call new XMLSerializer().serializeToString(document.body) to recover the real custom scheme URLs of installed browser extensions, leaking information about which extensions are installed and their internal URL structures.

🔒

Detailed vulnerability analysis & security impact assessment

Subscribe to read more

🔒

Pattern-based audit directions for variant discovery

Subscribe to read more