Medium CVSS 6.5 webkit Cross Origin 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionMaliciously crafted web content may violate iframe sandboxing policy
ComponentWebCore Loader
Bug ClassCross Origin
Tracker313220
Fix commitc52bbb5187e1 (WebKit/WebKit) +176/-0
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
Creditedan anonymous researcher
Disclosed2026-07-27

Background

SameSite cookies
A cookie attribute (Strict/Lax/None) that withholds cookies from cross-site requests to defend against CSRF and cross-site tracking.
site-for-cookies
The first-party URL WebKit computes per document to decide whether an outgoing request is same-site; getting it wrong misclassifies cross-site as same-site.
srcdoc / about:blank inheritance
These frames inherit their security origin from their owner; their real origin is the parent’s, not the top document’s.

Root Cause Analysis

This fixes an incorrect site-for-cookies computation that let SameSite-restricted cookies leak to cross-site requests from a srcdoc iframe, violating iframe/cookie policy. FrameLoader::setFirstPartyForCookies walks descendant frames and assigns each document’s siteForCookies. For a frame whose document inherits its security origin from its owner (about:srcdoc / about:blank — SecurityPolicy::shouldInheritSecurityOriginFromOwner), the pre-patch code lumped that case together with the registrable-domain match and, when it matched, set the srcdoc document’s siteForCookies to the top-level url. For a srcdoc iframe nested inside a CROSS-ORIGIN iframe, that is wrong: the srcdoc inherits its parent’s (cross-origin) origin, so requests it makes back to the top-level origin are cross-site and must not carry SameSite=Strict/Lax cookies. Because its site-for-cookies was set as if same-site with the top origin, those requests were treated as same-site and received the restricted cookies.

The fix instead, for the inherit-from-owner case, sets the frame document’s siteForCookies to its PARENT frame’s siteForCookies (parent->document()->siteForCookies()), so a srcdoc inside a cross-origin iframe is correctly cross-site to the top origin.

The restored invariant is that a srcdoc/about:blank frame’s first-party-for-cookies follows its actual parent, not the top document. The layout tests confirm SameSite Strict/Lax cookies are withheld from fetch and <img> requests from such a nested srcdoc while the SameSite=None cookie still flows. Established by the diff.

Key insight
The bug was in origin reasoning, not memory: the inherit-from-owner case (srcdoc/about:blank) was lumped with the registrable-domain match and given the top-level url as its site-for-cookies. The fix instead inherits the parent frame’s site-for-cookies, so a srcdoc nested inside a cross-origin iframe is correctly cross-site to the top origin.

Attack Path

  1. Embed a cross-origin iframe of the victim's site An attacker page (or a page the attacker controls a subframe of) loads a cross-origin iframe relative to the victim top-level origin.
  2. Create a nested srcdoc iframe That cross-origin iframe creates a srcdoc iframe, which inherits the cross-origin parent’s origin.
  3. Issue a request back to the victim origin The srcdoc performs a fetch()/<img> request to the victim’s top-level origin with credentials.
  4. Leak SameSite cookies cross-site Pre-patch the srcdoc’s site-for-cookies was the top origin, so the cross-site request was treated as same-site and carried SameSite=Strict/Lax cookies for the victim, leaking them (now withheld).

Impact Assessment

A cross-site cookie leak: SameSite=Strict/Lax cookies for a victim origin are sent on requests from a nested srcdoc iframe that is actually cross-site, enabling CSRF-style actions and cross-site information leakage that SameSite is meant to prevent. Exploitable purely from an attacker page embedding the victim — no memory corruption, high reliability.

Changed Functions

FunctionChangeNotes
FrameLoader::setFirstPartyForCookies
Source/WebCore/loader/FrameLoader.cpp
modified For frames that inherit their origin from the owner (srcdoc/about:blank), sets the document's siteForCookies to the parent frame's siteForCookies instead of the top-level url, so a srcdoc nested in a cross-origin iframe is correctly treated as cross-site for SameSite cookie purposes.

Files Changed

  • LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
  • LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe.html
  • LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
  • LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe.html
  • LayoutTests/http/tests/cookies/same-site/resources/record-image-cookies.py
  • LayoutTests/http/tests/cookies/same-site/resources/srcdoc-creator-img-inside-cross-origin-iframe.html
  • LayoutTests/http/tests/cookies/same-site/resources/srcdoc-creator-inside-cross-origin-iframe.html
  • Source/WebCore/loader/FrameLoader.cpp

Audit Directions

  • Origin-inheritance cases in the loader
    Audit every shouldInheritSecurityOriginFromOwner branch for security decisions (cookies, referrer, CSP) that use the top document instead of the actual inheriting parent.
  • Nested-frame first-party computation
    Test site-for-cookies for srcdoc/about:blank frames nested at varying depths inside cross-origin iframes, not just direct children of the top frame.
diff --git a/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe-expected.txt b/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
new file mode 100644
index 000000000000..2cb25ce2f4a6
--- /dev/null
+++ b/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
@@ -0,0 +1,13 @@
+Tests that SameSite=Strict and SameSite=Lax cookies for 127.0.0.1 are not sent with a fetch() initiated from a srcdoc iframe nested inside a cross-origin iframe. The srcdoc inherits its parent's origin, so a request from the srcdoc back to the top-level origin is cross-site and must not receive SameSite-restricted cookies.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Cookies sent with cross-site fetch initiated from srcdoc iframe inside cross-origin iframe:
+PASS Do not have cookie "strict".
+PASS Do not have cookie "lax".
+PASS Has cookie "implicit-default" with value 9.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe.html b/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe.html
new file mode 100644
index 000000000000..936fde8bb3e1
--- /dev/null
+++ b/LayoutTests/http/tests/cookies/same-site/fetch-in-srcdoc-iframe-inside-cross-origin-iframe.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script src="../resources/cookie-utilities.js"></script>
+</head>
+<body>
+<script>
+window.jsTestIsAsync = true;
+
+description("Tests that SameSite=Strict and SameSite=Lax cookies for 127.0.0.1 are not sent with a fetch() initiated from a srcdoc iframe nested inside a cross-origin iframe. The srcdoc inherits its parent's origin, so a request from the srcdoc back to the top-level origin is cross-site and must not receive SameSite-restricted cookies.");
+
+const kCookieValue = "9";
+
+function checkCookies(cookies)
+{
+    debug("Cookies sent with cross-site fetch initiated from srcdoc iframe inside cross-origin iframe:");
+    if (cookies.error) {
+        testFailed(`fetch from srcdoc failed: ${cookies.error}`);
+        return;
+    }
+    if (cookies.strict === undefined)
+        testPassed('Do not have cookie "strict".');
+    else
+        testFailed(`Should not have cookie "strict". But do with value ${cookies.strict}.`);
+
+    if (cookies.lax === undefined)
+        testPassed('Do not have cookie "lax".');
+    else
+        testFailed(`Should not have cookie "lax". But do with value ${cookies.lax}.`);
+
+    if (cookies["implicit-default"] === kCookieValue)
+        testPassed(`Has cookie "implicit-default" with value ${kCookieValue}.`);
+    else
+        testFailed(`Should have cookie "implicit-default" with value ${kCookieValue}. Got ${cookies["implicit-default"]}.`);
+}
+
+async function runTest()
+{
+    await resetCookies();
+    await setCookie("strict", kCookieValue, {"SameSite": "Strict", "Max-Age": 100, "path": "/"});
+    await setCookie("lax", kCookieValue, {"SameSite": "Lax", "Max-Age": 100, "path": "/"});
+    await setCookie("implicit-default", kCookieValue, {"SameSite": null, "Max-Age": 100, "path": "/"});
+
+    let cookiesPromise = new Promise((resolve) => {
+        window.addEventListener("message", (event) => {
+            if (event.data && event.data.type === "cookies-from-srcdoc")
+                resolve(event.data.cookies);
+        }, {once: true});
+    });
+
+    let attackerIframe = document.createElement("iframe");
+    attackerIframe.src = "http://localhost:8000/cookies/same-site/resources/srcdoc-creator-inside-cross-origin-iframe.html";
+    document.body.appendChild(attackerIframe);
+
+    let cookies = await cookiesPromise;
+    checkCookies(cookies);
+
+    document.body.removeChild(attackerIframe);
+
+    await resetCookies();
+    finishJSTest();
+}
+runTest();
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe-expected.txt b/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
new file mode 100644
index 000000000000..b1683955995f
--- /dev/null
+++ b/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe-expected.txt
@@ -0,0 +1,13 @@
+Tests that SameSite=Strict and SameSite=Lax cookies for 127.0.0.1 are not sent with an <img> sub-resource request initiated from a srcdoc iframe nested inside a cross-origin iframe. The srcdoc inherits its parent's origin, so the image request back to the top-level origin is cross-site and must not receive SameSite-restricted cookies.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Cookies sent with cross-site <img> request initiated from srcdoc iframe inside cross-origin iframe:
+PASS Do not have cookie "strict".
+PASS Do not have cookie "lax".
+PASS Has cookie "implicit-default" with value 9.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe.html b/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe.html
new file mode 100644
index 000000000000..16e76c2c5593
--- /dev/null
+++ b/LayoutTests/http/tests/cookies/same-site/img-from-srcdoc-iframe-inside-cross-origin-iframe.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="/js-test-resources/js-test.js"></script>
+<script src="../resources/cookie-utilities.js"></script>
+</head>
+<body>
+<script>
+window.jsTestIsAsync = true;
+
+description("Tests that SameSite=Strict and SameSite=Lax cookies for 127.0.0.1 are not sent with an &lt;img&gt; sub-resource request initiated from a srcdoc iframe nested inside a cross-origin iframe. The srcdoc inherits its parent's origin, so the image request back to the top-level origin is cross-site and must not receive SameSite-restricted cookies.");
+
+const kCookieValue = "9";
+const kRecorderBase = "/cookies/same-site/resources/record-image-cookies.py";
+const kToken = "srcdoc-img-test-" + Date.now();
+
+async function fetchRecordedCookies()
+{
+    let response = await fetch(`${kRecorderBase}?mode=read&token=${encodeURIComponent(kToken)}`, {credentials: "same-origin"});
+    return response.json();
+}
+
+async function resetRecordedCookies()
+{
+    await fetch(`${kRecorderBase}?mode=reset&token=${encodeURIComponent(kToken)}`, {credentials: "same-origin"});
+}
+
+function checkCookies(cookies)
+{
+    debug("Cookies sent with cross-site &lt;img&gt; request initiated from srcdoc iframe inside cross-origin iframe:");
+    if (cookies.strict === undefined)
+        testPassed('Do not have cookie "strict".');
+    else
+        testFailed(`Should not have cookie "strict". But do with value ${cookies.strict}.`);
+
+    if (cookies.lax === undefined)
+        testPassed('Do not have cookie "lax".');
+    else
+        testFailed(`Should not have cookie "lax". But do with value ${cookies.lax}.`);
+
+    // Positive control: WebKit treats a cookie with no SameSite attribute as
+    // SameSite=None (see coreSameSitePolicy() in CookieCocoa.mm), so it must
+    // still be sent on a cross-site image request that includes credentials.
+    // Without this assertion, the negative checks above could pass simply
+    // because cookies aren't being delivered at all.
+    if (cookies["implicit-default"] === kCookieValue)
+        testPassed(`Has cookie "implicit-default" with value ${kCookieValue}.`);
+    else
+        testFailed(`Should have cookie "implicit-default" with value ${kCookieValue}. Got ${cookies["implicit-default"]}.`);
+}
+
+async function runTest()
+{
+    await resetCookies();
+    await resetRecordedCookies();
+    await setCookie("strict", kCookieValue, {"SameSite": "Strict", "Max-Age": 100, "path": "/"});
+    await setCookie("lax", kCookieValue, {"SameSite": "Lax", "Max-Age": 100, "path": "/"});
+    await setCookie("implicit-default", kCookieValue, {"SameSite": null, "Max-Age": 100, "path": "/"});
+
+    let imgLoadedPromise = new Promise((resolve, reject) => {
+        window.addEventListener("message", (event) => {
+            if (!event.data)
+                return;
+            if (event.data.type === "srcdoc-img-loaded")
+                resolve();
+            else if (event.data.type === "srcdoc-img-error")
+                reject(new Error("srcdoc <img> failed to load"));
+        }, {once: false});
+    });
+
+    let attackerIframe = document.createElement("iframe");
+    attackerIframe.src = "http://localhost:8000/cookies/same-site/resources/srcdoc-creator-img-inside-cross-origin-iframe.html?token=" + encodeURIComponent(kToken);
+    document.body.appendChild(attackerIframe);
+
+    try {
+        await imgLoadedPromise;
+    } catch (e) {
+        testFailed(String(e));
+        document.body.removeChild(attackerIframe);
+        await resetCookies();
+        finishJSTest();
+        return;
+    }
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.