← All issues

CSP strict-dynamic does not block parser-inserted external module scripts without a nonce

97937c9

Source/WebCore/dom/ScriptElement.cpp

// requestModuleScript (external module path) — 수정 전에는 이 호출이 없었습니다:
+ if (!contentSecurityPolicy->allowScriptForStrictDynamic(*this))
+ return;

Source/WebCore/page/csp/ContentSecurityPolicy.cpp

-bool ContentSecurityPolicy::allowNonParserInsertedScripts(...) const
+bool ContentSecurityPolicy::allowScriptForStrictDynamic(...) const

strict-dynamic은 CSP의 script 평가 방식을 바꿉니다. URL allowlist 대신 valid nonce를 가진 script를 신뢰하고, 해당 script가 동적으로 생성하는 script까지 전이적으로 신뢰하는 방식입니다. 다만 parser-inserted script(DOM API가 아닌 HTML 마크업에 직접 작성된 script)는 이 전이적 신뢰에서 명시적으로 제외되며, 반드시 자체 valid nonce를 포함해야 합니다.

WebKit은 이를 두 단계 검사로 구현합니다. 먼저 allowScriptForStrictDynamic이 request 시점에 실행되어 parser-inserted script를 차단하고, 이후 allowScriptFromSource가 fetch 시점에 동작하는데, strict-dynamic이 활성화된 상태에서는 무조건 true를 반환하도록 되어 있습니다. 이는 첫 번째 단계가 이미 통과되었다는 전제에 기반합니다.

이 commit은 strict-dynamic 정책 하에서 parser-inserted external module script(<script type="module" src="...">)가 nonce 검사 없이 실행되던 CSP bypass를 수정합니다. requestModuleScript는 external module 경로를 처리하면서 첫 번째 단계를 전혀 호출하지 않았기 때문에, fetch 시점의 검사가 모든 parser-inserted module script를 조용히 통과시켰습니다. 수정 사항으로 ScriptElement::requestModuleScriptallowScriptForStrictDynamic 호출이 추가되었으며, 이는 classic script 및 inline module에 이미 적용된 방식과 동일합니다. 또한 함수명이 allowNonParserInsertedScripts에서 allowScriptForStrictDynamic으로 변경되었습니다.

script-src 'nonce-X' 'strict-dynamic'으로 강화된 페이지에서도, nonce가 없는 parser-inserted external module script는 차단되지 않았습니다. 해당 정책의 핵심 목적 자체가 무력화된 셈입니다. CSP를 XSS 방어 경계로 활용하는 모든 사이트가 영향을 받았습니다.

🔒

Other script-loading paths may share the same missing gate — audit directions for analogous bypasses are included.

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