CORS enforcement for app-registered custom URL schemes
The scheme the app opted into CORS never got a CORS check
Component: WebKit custom URL schemes | f2a07be
registerURLSchemeAsCORSEnabled()를 호출하면 앱이 정의한 custom scheme에도 http, https와 동일한 CORS 규칙을 적용할 수 있습니다. CORS 검사는 보통 두 곳에서 이뤄집니다. 하나는 NetworkProcess이고, 다른 하나는 WebProcess의 loader 코드입니다. 후자는 XHR과 fetch를 담당하는 DocumentThreadableLoader, subresource를 담당하는 CachedResourceLoader로 나뉩니다. 문제는 WKURLSchemeHandler가 처리하는 요청입니다. 이런 요청은 WebProcess 안에서 전부 해결되기 때문에, NetworkProcess의 CORS 처리 경로에는 아예 도달하지 않습니다.
이번 commit은 그렇게 벌어진 틈을 양쪽에서 모두 막았습니다. 먼저 NetworkProcess는 registerURLSchemeAsCORSEnabled()로 등록된 내역을 전달받은 적이 없었는데, 이제 해당 등록 정보가 NetworkProcess로 전달됩니다. WebProcess 쪽에서는 DocumentThreadableLoader와 CachedResourceLoader가 수정되었습니다. WKURLSchemeHandler가 지원하는 scheme을 대상으로 하는 no-cors cross-origin subresource 로드와 redirect가 더 이상 CORS 검사를 통째로 건너뛸 수 없게 되었습니다.
Significance
WKWebView에 로드된 cross-origin 웹 콘텐츠가 평범한 no-cors 요청만으로 앱의 custom scheme resource에서 opaque cross-origin 데이터를 읽어낼 가능성이 존재했습니다. no-cors 요청에는 preflight도, 응답 노출도 요구되지 않습니다. 그래서 패치 이전 경로에는 앱이 opt-in한 검사를 강제하는 지점이 어디에도 없었습니다. 한편 앱이 WKURLSchemeHandler로 custom URL scheme을 등록하는 목적은 대체로 번들 리소스나 로컬 콘텐츠를 제공하기 위해서입니다. 그런 만큼 해당 scheme 뒤에 놓인 리소스는 앱 자신의 privileged data인 경우가 많습니다.
Audit directions
앞으로 눈여겨볼 패턴은 frame tree 상의 위치를 기준으로 삼는 보안 예외 처리입니다. 정작 그 tree는 attacker가 재구성할 수 있습니다. Narrow: opaque origin을 다루는 분기는 문서가 앱에 의해 로드된 것인지 판단하기 위해 frame->tree().parent(), frame->opener(), frame->tree().top()을 따라 올라갑니다. 여기에 중첩 iframe, blob:과 srcdoc 문서, opener chain을 조합해 압박을 가해볼 필요가 있습니다. 각 경우마다 확인할 질문은 하나입니다. cross-origin 문서가 자신의 origin은 custom scheme과 다르게 유지하면서 topFrame->frameURLProtocol()만 custom scheme과 일치하도록 만들 수 있는가입니다. Wider: WebProcess 쪽 검사와 이번에 새로 전달되기 시작한 NetworkProcess 쪽 등록 정보는, 하나의 정책을 강제하는 두 개의 지점입니다. 그래서 등록 lifecycle의 모든 시점에서 두 지점이 어떤 scheme을 CORS-enabled로 보는지 서로 일치하는지 점검해야 합니다. 로드가 시작된 뒤에 등록되는 scheme, 또는 한쪽 process에만 등록되고 다른 쪽에는 아직 반영되지 않은 scheme이 바로 두 지점의 판단이 어긋나는 형태입니다. Widest: renderer 쪽 검사와 network 쪽 검사로 정책이 쪼개져 있다면, 둘의 판단이 갈릴 때 어느 쪽이 최종 권한을 갖는지에 대한 명시적인 답이 있어야 합니다. 코드 리뷰 관점에서는, 예외 처리가 전부 이른 return false로만 표현된 검사를 만났을 때 각 예외를 하나씩 짚어볼 만합니다. 이때 던질 질문은 그 예외의 전제 조건을 누가 제어하는가입니다.