← All issues

[2] CSP object-src empty-source-list bypass for no-URL plugin elements

Severity: Medium | Component: WebCore Content Security Policy enforcement | c6228ab

이 diff는 CSP object-src 정책의 bypass를 수정합니다. URL 없이 사용된 <object>/<embed> 인스턴스화에 대해 빈 source list가 deny-all이 아닌 허용으로 처리되던 문제였습니다. 이 버그는 배포된 정책에 의존하는 페이지의 plugin attack surface를 확대하지만, 직접적인 memory primitive는 발생하지 않습니다. Exploit을 위해서는 이미 HTML injection이 가능한 상태가 전제되어야 하며, 영향 범위는 'none' 대신 빈 source list 형태를 사용하는 CSP에 한정됩니다.

<object> 또는 <embed> 엘리먼트에 data/src 속성이 없는 경우, 기존 WebKit은 빈 URL을 CSP 검사에 전달했습니다. 이 경로에는 'none' 키워드가 명시된 경우에만 차단하는 특수 로직이 적용되어 있었습니다. 빈 source list(object-src;)는 CSP Level 3 §6.7.2.7에 따라 'none'과 동일하게 취급해야 하지만, 기존 코드는 이를 잘못 허용했습니다. 이번 패치는 §6.1.9 특수 케이스를 완전히 제거합니다. 대신 URL이 없는 엘리먼트에 대해서는 document 자체의 URL을 source-list 매칭의 fallback으로 사용합니다. Document URL은 빈 source list와 'none'에 대해 자연스럽게 매칭되지 않아 차단되고, 'self'나 wildcard에 대해서는 허용됩니다. 새로 추가된 WPT 테스트 3개는 URL 없이 사용된 <object>/<embed>object-src 'none'과 빈 source list 모두에서 차단되는지 검증합니다.

Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp

-bool ContentSecurityPolicySourceListDirective::allows(const URL& url, bool didReceiveRedirectResponse, ShouldAllowEmptyURLIfSourceListIsNotNone shouldAllowEmptyURLIfSourceListEmpty)
+bool ContentSecurityPolicySourceListDirective::allows(const URL& url, bool didReceiveRedirectResponse)
{
if (url.isEmpty())
- return shouldAllowEmptyURLIfSourceListEmpty == ShouldAllowEmptyURLIfSourceListIsNotNone::Yes && !m_sourceList.isNone();
+ return false;
return m_sourceList.matches(url, didReceiveRedirectResponse);
}

Source/WebCore/page/csp/ContentSecurityPolicy.cpp

- if (m_policies.isEmpty() || LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
+ const auto& urlToCheck = url.isEmpty() ? m_protectedURL : url;
+ if (m_policies.isEmpty() || LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(urlToCheck.protocol()))
return true;
- // ... 'MUST be blocked if object-src's value is 'none', but will otherwise be allowed' ...
String sourceURL;
- const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
+ const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : urlToCheck;
...
- return allPoliciesAllow(handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::Yes);
+ return allPoliciesAllow(handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource, urlToCheck, redirectResponseReceived == RedirectResponseReceived::Yes);

LayoutTests/imported/w3c/web-platform-tests/content-security-policy/object-src/object-src-no-url-empty-source-list-blocked.html

+<meta http-equiv="Content-Security-Policy" content="object-src; script-src 'self' 'unsafe-inline';">
+<object type="text/html"></object>

패치는 ContentSecurityPolicySourceListDirective::allows에서 ShouldAllowEmptyURLIfSourceListIsNotNone 파라미터를 제거합니다. 이제 이 함수는 빈 URL에 대해 무조건 false를 반환합니다. ContentSecurityPolicy::allowObjectFromSource에서는 전달된 url이 비어 있을 때(data/src가 없는 <object>/<embed>의 경우), 정책 매칭을 수행하기 전에 m_protectedURL(document 자체의 URL)로 대체합니다. 해당 파라미터는 checkSource(), violatedDirectiveForObjectSource(), 그리고 관련 allows() 오버로드 시그니처에서도 함께 제거되었습니다. checkFrameAncestors()의 두 오버로드에서도 제거된 세 번째 인자가 삭제되었습니다.

source-list 매칭의 empty-URL fast path에서 두 CSP 상태('none' 키워드와 빈 source list)를 혼용하여, 빈 source list가 deny-all이 아닌 허용으로 동작한 패턴.

Content Security Policy는 HTTP 헤더 또는 meta 태그를 통해 전달되는 정책으로, document가 각 리소스 유형에 대해 허용할 source를 제한하는 데 사용됩니다. object-src 디렉티브는 <object>/<embed> plugin 로딩을 제어합니다. source list는 디렉티브의 오른쪽 값으로, 'self'·'none'·scheme/host 표현식 등의 토큰을 포함하거나 비어 있을 수 있습니다. CSP Level 3 명세에 따르면 빈 source list는 어떤 URL도 매칭하지 않으며, 'none'과 동일하게 동작합니다.

WebKit에서는 ContentSecurityPolicySourceList로 source list를 모델링합니다. isNone()은 리터럴 'none' 토큰에 대해서만 true를 반환하며, 빈 리스트에 대해서는 false를 반환합니다. <object>/<embed>는 URL 없이도 plugin을 인스턴스화할 수 있는데, datasrc 속성이 모두 없는 경우에는 type 속성만으로 plugin이 선택됩니다. 초기 CSP 초안에는 이러한 no-URL plugin에 대해 'none'이 명시되지 않은 한 허용하는 특수 조항이 있었으며, 이것이 이번 버그의 배경이 되었습니다.

패치 이전 allows() 내부 조건은 shouldAllowEmptyURLIfSourceListEmpty == Yes && !m_sourceList.isNone()이었습니다. 이 조건의 목적은 §6.1.9 특수 케이스, 즉 URL 없는 plugin은 'none'이 명시된 경우에만 차단하고 그 외에는 허용한다는 원칙을 구현하는 것이었습니다. 그러나 이 조건은 두 가지 서로 다른 CSP 상태를 혼용합니다. (a) 명시적인 'none' 키워드와 (b) 빈 source list(object-src;)입니다.

CSP Level 3 §6.7.2.7에 따르면 이 둘은 의미상 동일합니다. 둘 다 어떤 URL도 매칭하지 않아야 합니다. 그러나 m_sourceList.isNone()은 리터럴 'none' 토큰에 대해서만 true를 반환합니다. 결과적으로 object-src; 정책에서는 Yes && !false = true가 되어, 배포된 정책이 모든 object source를 금지하는 의도임에도 no-URL <object>/<embed>가 기본 plugin을 로딩할 수 있었습니다.

🔒

The policy-enforcement implications of conflating two CSP states in a single matcher predicate, and the boundary this weakens when the policy is deployed as a deny-all.

더 확인하려면 구독해 주세요

🔒

Multiple reusable audit patterns identified across CSP source-list matching and similar two-condition policy gates, with concrete starting points for variant discovery.

더 확인하려면 구독해 주세요