[6] CSP Port-Matching Bypass via Unconditional Port-Equivalence Relaxation
Severity: Medium | Component: WebCore CSP | c82619c
관찰되는 영향은 port 443의 리소스가 port 80 제한 목적의 소스 표현식과 일치하는 CSP 정책 bypass입니다. 다만 HTTPS 소스 표현식에 port 80을 명시하는 CSP 설정은 드물기 때문에 실질적인 영향은 제한적입니다. schemeMatches → portMatches 간 정보 손실 메커니즘이 명확히 드러나는 점을 근거로 신뢰도는 0.92입니다.
Patch Details
패치는 schemeMatches()의 반환 타입을 단순 bool에서 SchemeMatchResult enum(NoMatch, Match, InsecureUpgradeMatch)으로 변경하고, ShouldUpgradePorts 파라미터를 통해 이 정보를 portMatches()에 전달하도록 리팩토링되었습니다. port 80과 443을 상호 교환 가능한 기본값으로 취급하는 port 동등성 로직은, 실제로 insecure-to-secure scheme 업그레이드가 발생한 경우에만 동작하도록 변경되었습니다.
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와 B가 일치합니다.
if (scheme == urlScheme)
- return true;
+ return SchemeMatchResult::Match;
- // host-sources는 direct-upgrade가 가능합니다.
+ // Step 1.2: A가 "http"이고 B가 "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가 "ws"이고 B가 "wss", "http", "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가 "wss"이고 B가 "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
+ // HTTPS URL이 반대 scheme의 기본 port 번호를 가진 HTTPS 소스 표현식과 매칭되지 않는지 확인합니다.
+ ["no", "script-src https://127.0.0.1:8000", "https://127.0.0.1:8443/..."],
+
+ // HTTPS URL이 HTTP 기본 port를 가진 WSS 소스 표현식과 매칭되지 않는지 확인합니다(secure scheme, port 업그레이드 없음).
+ ["no", "script-src wss://127.0.0.1:8000", "https://127.0.0.1:8443/..."],
scheme 매칭과 port 매칭 사이에서 정보가 소실되어, CSP port 동등성 완화가 의도된 전제조건 밖에서 동작하는 문제가 발생합니다.
Background
CSP source expression은 리소스 로딩을 허용할 출처를 지정합니다. 예를 들어 script-src https://host:80 지시자는 해당 host와 port에서 오는 스크립트만 허용해야 합니다. WebKit은 CSP3 명세에 따라 "scheme 업그레이드" 로직을 구현합니다. CSP 소스에서 insecure scheme(http)을 사용하지만 URL에서 이에 대응하는 secure scheme(https)을 사용하는 경우, 매칭이 허용됩니다. 이때 두 scheme의 기본 port가 달라지기 때문에 port 80과 443을 동등한 값으로 취급합니다. 이 port 동등성은 실제로 insecure-to-secure scheme 전환이 발생한 경우에만 적용되어야 합니다. ContentSecurityPolicySource::matches()는 schemeMatches(), hostMatches(), portMatches(), pathMatches()를 순서대로 호출합니다.
Analysis
패치 이전에는 URL이 secure scheme을 사용하고 소스 표현식의 port가 "업그레이드 가능"으로 보이는 경우, portMatches()가 port 업그레이드 로직을 무조건 적용했습니다. 핵심 결함은 isCurrentUpgradable의 첫 번째 조건에 있었습니다. m_port == defaultInsecurePort 조건은 scheme 제약 없이 port 값만 검사했습니다. 결과적으로 port 80을 가진 소스 표현식은 scheme 업그레이드가 발생하지 않는 https://host:80을 포함해 모두 업그레이드 가능한 것으로 간주되었습니다. schemeMatches()가 단순 bool을 반환했기 때문에, 정확한 일치(https↔https)인지 insecure→secure 업그레이드(http→https)인지를 구별하는 정보가 portMatches() 실행 전에 소실되었습니다.
구체적인 bypass 예시입니다. 소스 표현식 https://host:80과 URL https://host:443을 대입해 보겠습니다. schemeMatches()는 true를 반환합니다(정확한 일치). 이후 portMatches()에서 isUpgradeSecure는 true가 됩니다(port 443 == defaultSecurePort). isCurrentUpgradable도 true인데, m_port 80 == defaultInsecurePort라는 첫 번째 조건이 scheme 검사 없이 성립하기 때문입니다. 결과적으로 port 80 ≠ 443이고 scheme 업그레이드도 발생하지 않았음에도 매칭이 성립합니다.
a Aa Aaa Aa Aaa Aaaaaaaaaaaaaaa Aa Aaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaa Aa Aaaaa Aaaaaaaaaaaa Aa Aaaaaa Aaaaaa Aaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaa Aaaaa Aaa Aaa Aaaaaaa Aa Aaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa a Aaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaa Aaa Aaa Aaaaaa
Aaaa Aaaa Aaa Aaaa Aaaaa Aa Aaaa Aaa Aaa Aaa Aa Aaaa Aaaa Aaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaaa Aaaa Aaaa Aaa Aaa Aaaa Aaaaa Aaaa Aa Aaaaa Aaaaaa a Aaaa Aa Aaaaa Aa Aaa Aaa Aaaa Aaa Aaaa Aaaaaaa Aaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaa Aaa Aa Aaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaa Aaa Aaa Aaaaaa
a Aaaaaaaaaaaaaa Aaa Aa Aa Aaa Aaaaaaa Aaa Aaa Aa Aaaaaa Aaaa Aaaa Aaaa Aaa Aaa Aaaaa Aaa Aaaa Aa Aaaaa Aaaa Aaaa Aaaa
Aa Aaa Aaaa Aa Aa Aaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaa Aaaaaaaa Aaaa Aaa Aaa Aaaaaaa Aa Aaaa Aaaa Aa Aaaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaa Aa Aa Aaaa Aa Aaaa a Aa Aa Aaaaaaaa Aa Aaaa Aaa Aaa Aa a Aaa Aaa Aaaaa Aaaaa Aaa Aa Aa Aaa Aaa a Aaa Aa a Aaa a Aaaaaa
🔒Detailed trace through the port-matching logic reveals which specific conditions enabled the bypass and how the fix's information-flow restructuring eliminates it
더 확인하려면 구독해 주세요
Audit directions
a Aaaa Aaa Aa Aa Aaaaa Aaaa Aaa Aaaaa Aaaa Aa Aa Aa Aaaaa Aa Aaa Aa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aa Aaaaaa Aaa Aaaaaa Aaaaaaaaaa Aa Aaa Aaaa Aaa Aaa Aaa Aaaa Aaaa Aaaaaaa Aaa Aa Aaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aa Aaa Aaaaaaa
a Aaaaaaa Aaaa Aa Aaaaaa Aaaaa Aaaaa Aaa Aaa Aaaa Aa Aaaaa Aaaaaa Aaaaaa Aaa Aaa Aaa Aaaa Aa Aaaa Aa Aaaaaaaa Aaaaaaaa Aaaaa Aaaaaaaa Aa Aaaaaa Aa Aa Aa Aaaa Aaa Aa Aaaa Aaa Aaa Aaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaa
a Aaaa Aa Aaaaaaaaaaaaaaa Aa Aaaaa Aaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaa a Aaaaaaaa Aaaaa Aaaa Aaaaa Aaa Aaaa Aaaaa a Aaa Aaaaaa Aaaaaaaaa Aaaa Aaaaa Aaa Aaa Aaaaa Aa Aaaa Aaa Aaa Aaaaa Aaaa Aaaa
🔒Multiple audit patterns identified for similar information-loss bugs in browser policy matching code, with concrete search targets
더 확인하려면 구독해 주세요