[6] CSP Port-Matching Bypass via Unconditional Port-Equivalence Relaxation
Severity: Medium | Component: WebCore CSP | c82619c
Rated Medium because the observable effect is a CSP policy bypass allowing resources from port 443 to match a source expression intended to restrict to port 80, but the practical impact is limited by the rarity of CSP configurations that specify port 80 on an HTTPS source expression. Confidence is 0.92 based on the clearly demonstrated information-loss mechanism in schemeMatches → portMatches.
Patch Details
The patch refactors schemeMatches() to return a SchemeMatchResult enum (NoMatch, Match, InsecureUpgradeMatch) instead of a plain bool, and threads this information into portMatches() via a ShouldUpgradePorts parameter. The port-equivalence logic (treating port 80 and 443 as interchangeable defaults) now only fires when an actual insecure-to-secure scheme upgrade occurred.
Source/WebCore/page/csp/ContentSecurityPolicySource.cpp
- bool ContentSecurityPolicySource::schemeMatches(const URL& url) const
+ SchemeMatchResult ContentSecurityPolicySource::schemeMatches(const URL& url) const
{
// https://www.w3.org/TR/CSP3/#match-schemes.
- auto& scheme = m_scheme.isEmpty() ? m_policy->selfProtocol() : m_scheme;
+ const auto& scheme = m_scheme.isEmpty() ? m_policy->selfProtocol() : m_scheme;
auto urlScheme = url.protocol();
+ // Step 1.1: A matches B.
if (scheme == urlScheme)
- return true;
+ return SchemeMatchResult::Match;
- // host-sources can do direct-upgrades.
+ // Step 1.2: A is "http" and B is "https".
if (scheme == "http"_s && urlScheme == "https"_s)
- return true;
- if (scheme == "ws"_s && (urlScheme == "wss"_s || urlScheme == "https"_s || urlScheme == "http"_s))
- return true;
- if (scheme == "wss"_s && urlScheme == "https"_s)
- return true;
+ return SchemeMatchResult::InsecureUpgradeMatch;
+
+ // Step 1.3: A is "ws" and B is "wss", "http", or "https".
+ if (scheme == "ws"_s) {
+ if (urlScheme == "wss"_s || urlScheme == "https"_s)
+ return SchemeMatchResult::InsecureUpgradeMatch;
+ if (urlScheme == "http"_s)
+ return SchemeMatchResult::Match;
+ }
+
+ // Step 1.4: A is "wss" and B is "https".
+ if (scheme == "wss"_s && urlScheme == "https"_s)
+ return SchemeMatchResult::Match;
- bool ContentSecurityPolicySource::portMatches(const URL& url) const
+ bool ContentSecurityPolicySource::portMatches(const URL& url, ShouldUpgradePorts shouldUpgradePorts) const
{
...
- auto defaultSecurePort = WTF::defaultPortForProtocol("https"_s).value_or(443);
- auto defaultInsecurePort = WTF::defaultPortForProtocol("http"_s).value_or(80);
- bool isUpgradeSecure = (port == defaultSecurePort) || (!port && (url.protocol() == "https"_s || url.protocol() == "wss"_s));
- bool isCurrentUpgradable = (m_port == defaultInsecurePort) || (m_scheme == "http"_s && (!m_port || m_port == defaultSecurePort));
- if (isUpgradeSecure && isCurrentUpgradable)
- return true;
+ if (shouldUpgradePorts == ShouldUpgradePorts::Yes) {
+ auto defaultSecurePort = WTF::defaultPortForProtocol("https"_s).value_or(443);
+ auto defaultInsecurePort = WTF::defaultPortForProtocol("http"_s).value_or(80);
+ bool urlOnSecureDefaultPort = (urlPort == defaultSecurePort) || (!urlPort && (url.protocol() == "https"_s || url.protocol() == "wss"_s));
+ bool sourcePortIsUpgradable = !m_port || m_port == defaultInsecurePort || m_port == defaultSecurePort;
+ if (urlOnSecureDefaultPort && sourcePortIsUpgradable)
+ return true;
+ }
LayoutTests/.../script-src-parsing-implicit-and-explicit-port-number.html
+ // Tests that HTTPS URL does not match HTTPS source expression with opposite scheme's default port number.
+ ["no", "script-src https://127.0.0.1:8000", "https://127.0.0.1:8443/..."],
+
+ // Tests that HTTPS URL does not match WSS source expression with HTTP default port (secure scheme, no port upgrade).
+ ["no", "script-src wss://127.0.0.1:8000", "https://127.0.0.1:8443/..."],
Lost information flow between scheme matching and port matching causes a CSP port-equivalence relaxation to fire outside its intended precondition.
Background
Content Security Policy (CSP) source expressions specify allowed origins for resource loading. A directive like script-src https://host:80 should only allow scripts from that exact host and port. WebKit implements "scheme upgrade" logic per the CSP3 spec: when a CSP source uses an insecure scheme (http) but the URL uses the secure equivalent (https), the match is allowed and default ports are treated as equivalent (80↔443) since they differ across schemes. This port equivalence should only apply when an actual insecure-to-secure scheme transition occurs. ContentSecurityPolicySource::matches() calls schemeMatches(), hostMatches(), portMatches(), and pathMatches() in sequence.
Analysis
Before the fix, portMatches() unconditionally applied port upgrade logic whenever the URL used a secure scheme and the source expression's port looked "upgradable." The critical flaw was in isCurrentUpgradable: its first disjunct (m_port == defaultInsecurePort) checked only the port value with no scheme constraint. Any source expression with port 80 was considered upgradable — including https://host:80 where no scheme upgrade occurred. Because schemeMatches() returned a plain bool, the information about whether the match was exact (https↔https) or an insecure→secure upgrade (http→https) was lost before portMatches() ran.
Concrete bypass: source expression https://host:80 against URL https://host:443. schemeMatches() returns true (exact match). In portMatches(): isUpgradeSecure = true (port 443 == defaultSecurePort), isCurrentUpgradable = true (m_port 80 == defaultInsecurePort — first disjunct, no scheme check). Result: match, despite port 80 ≠ 443 and no scheme upgrade having occurred.
a Aaaaaa Aaaaaaaa Aaaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaaaaa Aaaaaaaaaaa Aa a Aaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaa Aaaaa Aaaaaaa Aaaaaaa Aaaaa Aaaaaaaaaaaaaaaa Aaaaa Aaa Aaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaa Aaaaaaaaaaaa
Aa Aaaaaaaa Aaaaa a Aaaaaa Aaaa Aaaa Aaaa a Aaa Aaaaaa Aaaa Aa Aaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaa Aaa Aaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaa Aaaa Aaa Aaa Aaaaaaaaaaaa Aaaa Aaaaaaaaa Aaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaaaaa Aa Aaaaaaaaa Aaaa Aaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaa Aa Aaaaaaaaa Aa Aaaaaaa Aaaaaaaaaaaa Aa Aaaa Aaaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaaaaa Aaaaaaaaa Aaa Aaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaa Aaaa Aaaa Aaaaaa Aaa Aaa Aaaa Aaaaaa Aaaaaaaa Aaaaaaaaa Aaaa a Aaaa Aaa Aaaaaa Aaaaaaaa Aa Aaaaaa
Aaa Aaaa Aaaaa Aa a Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaa a Aaaaaaa Aaaaaa Aaaaaa Aaaaa Aaa Aaaaaaaa Aaa Aaaaaaaaa Aa Aa a Aaaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaa Aaa Aa Aaa Aaa Aaaaaaaa Aaaaaaa a a Aaaaa Aaaaaaaa Aaaa Aaaaaa Aaaa Aaaaa Aaaaaaa Aaa Aaaaaa Aaaaaaaaaa a Aa a Aaaaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaa Aaaaa a Aaaaaaaaa Aaaaaaaaaaaaa
🔒Detailed trace through the port-matching logic reveals which specific conditions enabled the bypass and how the fix's information-flow restructuring eliminates it
Subscribe to read more
Audit directions
a Aaaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaa Aaaa Aaaaaa Aaaa Aaaaa Aaaaaaaaaa Aaaaa Aaaaa Aa Aaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaa Aaaaa Aaa Aaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaaaa Aaa Aaaaa Aaaaa Aaa Aaaa Aa Aaaaa Aaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaa Aa Aaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaa Aaaa Aaaaa Aaaaaa Aaaaaaa Aaa Aaaaa
a Aaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaa Aaa Aaaaaa Aaaaaaaaaaaaaaaa Aaa Aaa Aaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aa Aaaaaaaaaaa Aa a Aaaaaa Aaaaaaa Aaaaaa Aaaaaaaaa Aaaaa Aaaaa Aaaaaaa Aaaaaa Aaaaaaaaaaaaaaa Aaaaaa Aaaaaaaa Aaaaa Aaaaaa Aaa Aaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaa Aaaaa Aaaaaaa Aaaaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaa Aaa Aaaaaaaaaaaaa Aaaa Aaa Aaaaaaaaaaaa Aaaa Aaaaaaa Aa Aaaaaaa Aaaaa Aaaaaaaa Aaaaaaa Aaaa Aaaaaaa Aaaaa Aaaa Aaa Aaaaa Aaaaaaa Aaaa Aaaaaaa Aaa Aaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaa Aa Aaaaaa Aaaaaaaaa Aaaaaaaa
🔒Multiple audit patterns identified for similar information-loss bugs in browser policy matching code, with concrete search targets
Subscribe to read more