[5] CSP path matching bypassed via percent-encoded slashes
Severity: Medium | Component: WebCore CSP path matching | 9a19d07
diff가 수정하는 내용은 허용된 origin에서 별도의 URL injection primitive와 결합 시 매번 재현 가능한 CSP 경로 제한 우회입니다. 영향 범위는 policy bypass에 국한되며, memory corruption은 아닙니다. 이 때문에 Medium으로 평가합니다.
WebKit의 pathMatches()는 CSP3 명세와 달리 동작했습니다. URL path 전체를 단일 flat string으로 percent-decode한 뒤 prefix/equality 비교를 수행하는 방식이었으며, 이 구조는 %2F..%2F를 이용한 path traversal 우회에 취약했습니다. 수정된 구현은 명세의 알고리즘(§6.7.2.12)을 채택합니다. 두 경로를 모두 literal /로 분리하고, 각 segment를 개별적으로 percent-decode한 뒤 대응하는 쌍을 비교합니다.
Source/WebCore/page/csp/ContentSecurityPolicySource.cpp
bool ContentSecurityPolicySource::pathMatches(const URL& url) const
{
+ // https://www.w3.org/TR/CSP3/#match-paths
if (m_path.isEmpty())
return true;
- auto path = PAL::decodeURLEscapeSequences(url.path());
+ auto urlPath = url.path();
+
+ if (m_path == "/"_s && urlPath.isEmpty())
+ return true;
+
+ bool exactMatch = !m_path.endsWith('/');
+ auto pathListA = m_path.splitAllowingEmptyEntries('/');
+ auto pathListB = urlPath.toString().splitAllowingEmptyEntries('/');
+
+ if (pathListA.size() > pathListB.size())
+ return false;
+ if (exactMatch && pathListA.size() != pathListB.size())
+ return false;
+ if (!exactMatch) {
+ ASSERT(pathListA.last().isEmpty());
+ pathListA.removeLast();
+ }
- if (m_path.endsWith('/'))
- return path.startsWith(m_path);
+ for (unsigned i = 0; i < pathListA.size(); ++i) {
+ if (PAL::decodeURLEscapeSequences(pathListA[i]) != PAL::decodeURLEscapeSequences(pathListB[i]))
+ return false;
+ }
- return path == m_path;
+ return true;
}
Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp
- return PAL::decodeURLEscapeSequences(begin.first(buffer.position() - begin.data()));
+ return String(begin.first(buffer.position() - begin.data()));
Patch Details
기존 구현은 URL path 전체를 하나의 flat string으로 percent-decode한 뒤 m_path에 대해 startsWith/equality 비교를 수행했습니다. 새 구현은 m_path와 url.path() 모두를 splitAllowingEmptyEntries('/')를 통해 literal / 기준으로 분리합니다. 이후 명세의 1~7단계를 적용하는데, 빈 path fast path, / vs empty 처리, directory/exact match 판별, segment 개수 비교, 후행 빈 segment 제거가 이에 해당합니다. 비교 직전에만 각 segment를 개별적으로 percent-decode합니다. 보완적으로, parsePath()는 더 이상 parse 시점에 directive의 path를 percent-decode하지 않습니다. m_path는 이제 raw %2F 시퀀스를 그대로 유지하므로, segment 분리가 의미 있게 동작합니다.
Path matcher에서의 decode-before-tokenise 순서 문제: URL path 전체를 '/'로 분리하기 전에 percent-decode하면, 인코딩된 슬래시(%2F)가 access-control policy가 정의하는 구조적 segment 경계를 넘어설 수 있습니다.
Background
CSP directive는 source expression 목록을 포함하며, 각 expression은 scheme, host, port, 그리고 선택적 path 구성요소를 갖습니다. URL fetching 과정에서는 ContentSecurityPolicySource::matches()가 호출되어 scheme, host, port, path를 순서대로 검사합니다. CSP3 §6.7.2.12는 path matching을 segment 단위 연산으로 정의합니다. 두 경로를 모두 literal /로 분리하고 각 segment 쌍을 percent-decode한 뒤 비교하는 방식입니다. /로 끝나는 directive는 directory match, 그렇지 않으면 exact match로 구분합니다.
%2F는 /의 percent-encoding입니다. URL 표준은 path 구성요소에서 %2F를 구조적 구분자로 해석하지 않고 그대로 유지합니다. 따라서 /a%2Fb/c는 세 개가 아닌 두 개의 segment(a%2Fb와 c)를 갖습니다. 반면 PAL::decodeURLEscapeSequences는 segment 경계를 인식하지 못하고 %2F를 그대로 literal /로 변환합니다.
Analysis
pathMatches()는 URL path를 전체적으로 percent-decode한 상태에서 비교를 수행했습니다. PAL::decodeURLEscapeSequences(url.path())는 %2F를 literal /로 변환합니다. 예를 들어 공격자가 http://host/security/contentSecurityPolicy%2F..%2F..%2Fresources/script.js와 같은 URL을 사용하면, 이는 /security/contentSecurityPolicy/../../resources/script.js로 decode됩니다. decode된 문자열은 /security/contentSecurityPolicy/로 시작하므로 startsWith(m_path)를 통과합니다. 그러나 CSP3의 segment 단위 정의에 따르면, URL의 실제 path segment는 단일 literal blob인 contentSecurityPolicy%2F..%2F..%2Fresources이며, 이는 /security/contentSecurityPolicy/resources/ 하위에 위치하지 않습니다.
Aaaa Aaaaaaa Aaa Aaaa Aaaa Aaa a Aa Aaa Aaaaaa Aa Aa Aaaaaaaa Aaaaaaaaa Aaa Aaa a Aa Aaaa Aaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaa Aa Aa Aaaaaa Aa Aaaa Aaaa Aaa Aaaa Aaa a Aaaaa a Aa Aaaaaaaaaa Aaaaaaaa Aaaaaaa Aa Aaaaaaa Aaaaaaa Aa Aaaaaaaaaaaaa Aaaa Aaaa Aa Aa Aaa Aaaa Aaaaaa
Aaa Aaa Aaaa Aaaa Aa Aaa Aaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aa Aaa Aaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaa Aaaa Aaaaaa Aa a Aa Aaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaa Aaaa Aaaaa
a Aaaaaaaaaaaaaa Aaa Aa Aaa Aa Aaa Aaaaaaaa Aaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaa Aaaaaaaa Aa Aaa Aaaaaa Aaaa Aaaaa Aaaaaa Aa Aaaa Aaaa Aaaaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaaa Aa Aaa Aaaaa Aa Aaaa Aaa Aa a Aa Aaaa a Aaaa Aaa a Aaaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaa Aaa Aaa Aa Aaaaaaa Aa Aaaa Aaaaaaa Aaaa Aa Aaaaaaa Aaa Aaaa Aaaaaaaaa a Aa Aaaa Aaa a Aa Aaaaaaa Aaaa Aaa Aaaaaaaaaa Aa Aaaaaa Aa Aaaaa Aaa Aaaa Aaa a Aa Aaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaa Aaaaaaa a Aaa Aaaaaaa Aaaa Aa Aa Aaa Aaaaa Aa Aaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaa Aaa Aa Aaaaa Aaaa Aaaa Aa Aaa Aaaa Aaaa Aaaaa Aaaa Aa Aaaa Aaaaaa
🔒The decode-then-compare ordering at the heart of this bug is one of the oldest anti-patterns in web security — the analysis traces exactly which URLs slipped through and why CSP3's spec deliberately reverses the order.
더 확인하려면 구독해 주세요
Audit directions
a Aaaaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaa Aaa Aaa Aa Aaa Aaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa Aaaa Aaa Aa Aaaaaaaaaaaaaaaaaaaaa a Aaa Aaaaa Aaaaaa Aaa Aaaa Aaa Aaaaa Aaa Aaa Aaaaa Aa Aa Aaaaa Aaaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaaa Aaaaaaaa Aaaaaaaaaaa Aaaaaaaa Aa Aaaaa
a Aaaaa Aaa Aaa Aaaa Aa Aaa Aaaa Aaaaaaaaa Aaaaa Aa Aaaa Aaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaaaaaa Aaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaa Aaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaaaa Aaaaa Aaaaaaaa a Aaa Aaaaaa Aaaa Aaaa Aaa Aaa Aaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaa Aaaa a Aaa Aaaaaaaaaaaaaaa Aaaaaaaaaaa Aa Aaa Aaaa Aa Aaa Aaaa Aaaa
a Aaaaaaaa Aa Aaa Aa Aa Aa Aaaaaa Aaaaaa Aaaaaaaaaaa Aaa Aaaaa Aaa Aa Aaaaaaa Aaa Aaa Aa Aaa Aa Aaa Aaaa Aa a Aaa Aaaaaa Aa Aaa a Aaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaa Aaa Aaaa Aaaa Aaa Aaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaa Aaa Aaaa Aaa a Aa Aa Aaaa Aaa Aaaaaa
a Aaaaaaaaaaaaaaaa Aaaaa a Aa Aaaaaa Aa Aaa Aaaaaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaaa Aaa Aaaaa Aa Aa Aaaaaa Aaaa Aaaaa Aa Aaa Aa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaaa Aaa Aaaaaa Aa Aaaaaaaaaaaaa Aaa Aaa Aaa Aaaaa
🔒Four reusable audit directions for percent-encoded canonicalisation bypasses across WebKit policy layers, with specific starting points beyond CSP path matching.
더 확인하려면 구독해 주세요