CVE-2024-44296
Overview
Impact:
Description: Processing maliciously crafted web content may prevent Content Security Policy from being enforced
Researchers: Narendra Bhati, Manager of Cyber Security at Suma Soft Pvt. Ltd, Pune (India)
Key insight. A request type (hyperlink auditing pings) was omitted from CSP connect-src enforcement, so the fix is simply to gate sendPing() through allowConnectToSource; the root cause is an incomplete enumeration of CSP-checked connection sinks rather than any flaw in CSP itself.
Patch Details
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt
new file mode 100644
index 000000000000..bc9f2dc74e91
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt
@@ -0,0 +1 @@
+Test Page. Some Text.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html
new file mode 100644
index 000000000000..2dc38db1c6f2
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="connect-src https://127.0.0.1:8443 https://localhost:8443">
+<script>
+if (window.testRunner) {
+ window.testRunner.waitUntilDone();
+ window.testRunner.dumpAsText();
+}
+function runTest() {
+ document.getElementById('an').click();
+}
+</script>
+</head>
+<body onload="runTest()">
+<a id="an" href="https://localhost:8443/security/contentSecurityPolicy/resources/sample.html" ping="https://localhost:8443">Click It</a>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt
new file mode 100644
index 000000000000..6f08de92e12f
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Refused to connect to https://localhost:8443/ because it does not appear in the connect-src directive of the Content Security Policy.
+Test Page. Some Text.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html
new file mode 100644
index 000000000000..a1096ca1d308
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="connect-src https://127.0.0.1:8443">
+<script>
+if (window.testRunner) {
+ window.testRunner.waitUntilDone();
+ window.testRunner.dumpAsText();
+}
+function runTest() {
+ document.getElementById('an').click();
+}
+</script>
+</head>
+<body onload="runTest()">
+<a id="an" href="https://localhost:8443/security/contentSecurityPolicy/resources/sample.html" ping="https://localhost:8443">Click It</a>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html
new file mode 100644
index 000000000000..b27198689ac9
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html
@@ -0,0 +1,12 @@
+<html>
+ Test Page.
+<script>
+ function finishTest(){
+ if (window.testRunner)
+ testRunner.notifyDone();
+ }
+</script>
+<body onload="finishTest()">
+<a>Some Text.</a>
+</body>
+</html>
\ No newline at end of file
diff --git a/Source/WebCore/loader/PingLoader.cpp b/Source/WebCore/loader/PingLoader.cpp
index 0768aeea1928..7f5344556528 100644
--- a/Source/WebCore/loader/PingLoader.cpp
+++ b/Source/WebCore/loader/PingLoader.cpp
@@ -134,6 +134,8 @@ void PingLoader::sendPing(LocalFrame& frame, const URL& pingURL, const URL& dest
#endif
Ref document = *frame.document();
+ if (!document->checkedContentSecurityPolicy()->allowConnectToSource(pingURL))
+ return;
document->checkedContentSecurityPolicy()->upgradeInsecureRequestIfNeeded(request, ContentSecurityPolicy::InsecureRequestType::Load);
request.setHTTPMethod("POST"_s);
Background
Advisory: Apple Security Advisory
| Attribute | Value |
|---|---|
| CVE | CVE-2024-44296 |
| Bugzilla | 278765 |
| Component | WebKit |
| Bug Class | Bypass |
| Severity | medium |
| Commit | 03fe2d2f0fa8939b… |
| Advisory | Apple Advisory |
Terminology
- Content Security Policy (CSP) — A per-document policy that restricts which origins the page may load or connect to, enforced by the browser to limit injection and exfiltration.
- connect-src directive — The CSP directive controlling script-initiated connections such as fetch, XHR, WebSocket, EventSource, and beacon/ping requests.
- Hyperlink auditing ping — The ping attribute on <a> elements causes the browser to send a POST notification to the listed URL(s) when the link is followed.
- PingLoader::sendPing — The WebCore routine that constructs and dispatches ping/auditing POST requests on behalf of a frame’s document.
- allowConnectToSource — The ContentSecurityPolicy method that returns whether a given URL is permitted by connect-src, emitting a refusal console message when it is not.
Root Cause Analysis
The patch modifies PingLoader::sendPing() in Source/WebCore/loader/PingLoader.cpp, the code path that issues hyperlink auditing pings (the ping attribute on anchors, and related beacon-style POSTs). Before the fix, sendPing() obtained the document and immediately called upgradeInsecureRequestIfNeeded() and built the POST request without ever consulting the document’s Content Security Policy about whether the ping destination was an allowed connection target. The fix inserts, right after Ref document = *frame.document(), a guard: if (!document->checkedContentSecurityPolicy()->allowConnectToSource(pingURL)) return;. The violated invariant is that network requests initiated on behalf of a document must be vetted against that document’s CSP, and specifically that ping/auditing requests fall under the connect-src directive (the same directive governing fetch/XHR/WebSocket/beacon connections). Because sendPing() skipped allowConnectToSource(), a page could send a ping to a host that its own connect-src policy forbade, so CSP was not enforced for this request type. The added layout tests confirm the semantics precisely: connect-src-ping-allowed.html declares connect-src including localhost:8443 and expects the ping to proceed, while connect-src-ping-blocked.html omits localhost:8443 and expects the console message ‘Refused to connect to https://localhost:8443/ because it does not appear in the connect-src directive of the Content Security Policy.’ The fix restores the invariant by consulting CSP and returning early (silently dropping the ping) when the destination is not permitted, matching how other connect-src-governed loads are gated.
Attack Path
1. Author a page under a restrictive CSP
The attacker (or attacker-controlled content on a site) operates in a document whose Content-Security-Policy sets a connect-src that is intended to prevent connections to certain hosts.
2. Emit a ping to a forbidden host
Place an anchor with a ping attribute (or otherwise trigger sendPing) targeting a URL not permitted by connect-src, e.g. <a href=… ping=‘https://forbidden-host’>.
3. Trigger the navigation/click
Cause the ping to fire (user click or scripted click()), reaching PingLoader::sendPing().
4. Bypass the policy
Because sendPing() did not call allowConnectToSource, the POST ping is sent to the forbidden host despite connect-src, exfiltrating a beacon/notification the policy was meant to block.
5. Abuse the covert channel
Use the un-vetted ping as a data exfiltration or tracking channel that CSP was supposed to close; this is a policy-enforcement bypass, not memory corruption.
Impact Assessment
This is a security-policy enforcement bypass, not a memory-safety issue: the primitive is the ability to send a ping POST to a host that the page’s connect-src should have blocked, usable for tracking or small-scale exfiltration/beaconing and to defeat a CSP-based containment expectation. There is no memory corruption, no OOB/UAF, and no direct path to code execution; the impact is confined to weakening a policy control within the WebContent process’s networking on behalf of the document. Severity is medium precisely because it undermines a defense-in-depth control rather than granting new memory capabilities.
Audit Directions
Audit PingLoader’s other senders
In PingLoader.cpp check sendViolationReport, sendBeacon-related paths, and any other request-emitting function for a matching allowConnectToSource / CSP check; grep sendPing, sendViolationReport, allowConnectToSource in that file.
Enumerate all connect-src sinks
Grep across Source/WebCore for network requests that should be connect-src-governed (fetch, XHR, WebSocket, EventSource, beacon, ping, report-uri/report-to) and confirm each calls allowConnectToSource before dispatch.
Find loaders that skip CSP entirely
Search loader code for request construction that calls upgradeInsecureRequestIfNeeded or ResourceLoadNotifier without a preceding CSP allow* call, a tell that a request sink was added without policy gating.
Check other CSP directives for missing sinks
Apply the same audit to img-src, media-src, frame-src, and form-action by grepping the corresponding allow* methods (allowImageFromSource, allowFormAction, etc.) against the code paths that actually initiate those loads.
Changed Functions
| Function | File | Change | Note |
|---|---|---|---|
PingLoader::sendPing |
Source/WebCore/loader/PingLoader.cpp |
modified | Adds an early-return CSP check calling checkedContentSecurityPolicy()->allowConnectToSource(pingURL) so ping requests are gated by connect-src like other connections; previously the check was absent. |
connect-src-ping-allowed test |
LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html |
added | Verifies a ping to a host present in connect-src is permitted. |
connect-src-ping-blocked test |
LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html |
added | Verifies a ping to a host absent from connect-src is refused with a CSP console message. |
sample resource / expected results |
LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html (+ *-expected.txt) |
added | Supporting navigation target and expected-output baselines for the allowed/blocked tests. |
Regression Test
The fix ships this proof-of-concept / regression test:
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt
new file mode 100644
index 000000000000..bc9f2dc74e91
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed-expected.txt
@@ -0,0 +1 @@
+Test Page. Some Text.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html
new file mode 100644
index 000000000000..2dc38db1c6f2
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-allowed.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="connect-src https://127.0.0.1:8443 https://localhost:8443">
+<script>
+if (window.testRunner) {
+ window.testRunner.waitUntilDone();
+ window.testRunner.dumpAsText();
+}
+function runTest() {
+ document.getElementById('an').click();
+}
+</script>
+</head>
+<body onload="runTest()">
+<a id="an" href="https://localhost:8443/security/contentSecurityPolicy/resources/sample.html" ping="https://localhost:8443">Click It</a>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt
new file mode 100644
index 000000000000..6f08de92e12f
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Refused to connect to https://localhost:8443/ because it does not appear in the connect-src directive of the Content Security Policy.
+Test Page. Some Text.
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html
new file mode 100644
index 000000000000..a1096ca1d308
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/connect-src-ping-blocked.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Security-Policy" content="connect-src https://127.0.0.1:8443">
+<script>
+if (window.testRunner) {
+ window.testRunner.waitUntilDone();
+ window.testRunner.dumpAsText();
+}
+function runTest() {
+ document.getElementById('an').click();
+}
+</script>
+</head>
+<body onload="runTest()">
+<a id="an" href="https://localhost:8443/security/contentSecurityPolicy/resources/sample.html" ping="https://localhost:8443">Click It</a>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html
new file mode 100644
index 000000000000..b27198689ac9
--- /dev/null
+++ b/LayoutTests/http/tests/security/contentSecurityPolicy/resources/sample.html
@@ -0,0 +1,12 @@
+<html>
+ Test Page.
+<script>
+ function finishTest(){
+ if (window.testRunner)
+ testRunner.notifyDone();
+ }
+</script>
+<body onload="finishTest()">
+<a>Some Text.</a>
+</body>
+</html>
\ No newline at end of file