[Site Isolation] Authorize cookie IPC against the sites a web process hosts
Site isolation gave iframes their embedder as first party, and its cookies too.
Component: WebKit Site Isolation | a5cb75b
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
Source/WebKit/UIProcess/WebProcessProxy.cpp
Site isolation 환경에서는 cross-site iframe이 별도의 WebContent process에서 실행됩니다. 이때 cookie store를 보유하는 주체는 network process 하나뿐입니다.
기존에는 network process가 cookie IPC(cookiesForDOM, setCookiesFromDOM, GetRawCookies, SetRawCookie, DeleteCookie, domCookiesForHost 등)를 승인할 때 renderer가 전달한 firstParty만 검사했습니다.
문제는 site isolation이 out-of-process iframe에 embedder의 domain을 허용된 first party로 의도적으로 부여한다는 점입니다.
반면 cookie는 url을 기준으로 선택되는데, 이 값은 전혀 검사되지 않았습니다.
이번 commit에서는 UI process가 process별 hosted-domain 집합(WebProcessProxy::hostedDomains)을 network process로 전송하도록 변경되었습니다.
그리고 validateCookieAccess는 요청 url의 registrable domain이 이 집합에 포함된 경우에만 접근을 허용합니다.
cookieRequestHeaderFieldValueDigest는 AllowUnhostedURL::Yes를 통해 이 검사에서 제외되었습니다.
또한 domCookiesForHost가 std::optional로 응답하도록 수정되었습니다. 그 결과 WebCookieCache가 거부 응답을 빈 cookie jar로 cache하지 않게 됩니다.
다만 일부 경우에는 검사가 permissive(std::nullopt)하게 유지됩니다.
site isolation이 꺼져 있을 때, process가 site isolation 용도로 사용되기 전, single-process mode, web archive, remote-worker process가 이에 해당합니다.
Before:
iframe proc (evil.com) --IPC(firstParty=embedder, url=embedder)--> NetworkProcess
validate(firstParty) OK --> returns embedder's cookies (HttpOnly too)
After:
UIProcess: hostedDomains() --setHostedDomains--> NetworkProcess
iframe proc --IPC(firstParty=embedder, url=embedder)--> validate(firstParty) OK
hostsDomain(RegistrableDomain{url})?
nullopt set | empty domain | contains() --> allow
otherwise --> Disallow (domCookiesForHost: nullopt)
Significance
패치 이전에는 compromise된 cross-site iframe process가 embedder의 URL을 지정해, HttpOnly를 포함한 해당 site의 cookie를 IPC로 읽거나 쓸 수 있었습니다. 가치가 높은 대상 위에 새로운 security boundary가 세워진 셈입니다. 다만 allowlist 방식으로 구현되었고, 의도적으로 fail-open되는 경로가 여러 개 포함되어 있습니다.
Audit directions
- Fail-open되는 allowlist.
m_hostedDomains == nullopt인 경우와domain.isEmpty()인 경우 모두 접근이 허용됩니다. 어떤 URL이 빈RegistrableDomain을 생성하는지 열거해 볼 필요가 있습니다. file:, data:, blob:, about:, IP host, localhost, 특이한 public-suffix 사례 등이 후보입니다. 아울러 호출자가url을 골라 이 경우에 해당하도록 만들 수 있는지도 점검해야 합니다. - 적용 범위. 검사는
validateCookieAccess에만 존재합니다. 따라서 cookieStore async API를 포함해 cookie를 다루는 모든 message가 이 함수를 거치는지 확인해야 합니다. 한편 거부 분기에는 API test가 없습니다. 이 읽기 요청을 위조할 수 있는 주체는 out-of-process iframe뿐인데, IPC testing API는 main frame에만 주입되기 때문입니다. - 비동기 정책 전파.
sendHostedDomainsToNetworkProcess는 commit 및 navigation과 비동기로 동작합니다. 이 경우 process는 이미 특정 site를 hosting하고 있지만, network process 쪽 집합은 아직 stale하거나 nullopt인 window가 생길 수 있습니다. 이런 구간이 존재하는지 살펴볼 필요가 있습니다. - 집합의 lifetime. Committed site는 frame이 사라진 뒤에도 유지됩니다. unload handler가 cookie를 사용할 수 있기 때문입니다. 또한
HostedSiteTracking은 앞으로만 전이되며,m_sharedProcessDomains는didStartUsingProcessForSiteIsolation에서 초기화됩니다. 재사용된 process를 이후에 사용하는 tenant에게 stale committed site가 접근 범위를 넓혀 주는지 점검해야 합니다. - 검사 예외 경로의 oracle.
cookieRequestHeaderFieldValueDigest는 임의의 cross-siteurl을 받습니다. 게다가 salt는 network process 실행 단위로 생성되며 web process 간에 공유됩니다. 반복 호출을 통해 compromise된 renderer가 다른 site cookie의 변경 여부를 판별하는 oracle을 얻을 수 있는지 확인할 필요가 있습니다. 그 밖의DomCookiesForHost사용처가 nullopt를 잘못 처리하지 않는지도 함께 살펴봐야 합니다.