[1] LoadImageForDecoding scheme and cookie-access validation
The decode endpoint that checked a URL was valid — and nothing else.
Severity를 High로 평가한 이유는, 패치 이전 endpoint가 WebContent로부터 attacker-controlled ResourceRequest를 그대로 받아들이면서 url.isValid()만 검증했기 때문입니다. 이로 인해 compromised renderer가 file:// load에 도달하고 임의의 cookie first-party를 NetworkProcess에 주장할 수 있는 경로가 열려 있었습니다. 실제 local-file 유출이나 cross-origin credentialed read로의 확장 여부는 diff에 드러나지 않는 downstream loader 동작에 달려 있습니다. 다만 이번 fix는 바로 그 경로에 필요한 scheme 검증과 cookie 인가 검사를 정확히 추가하고 있습니다.
LoadImageForDecoding은 url.isValid() 검사 하나만으로 임의의 ResourceRequest 필드를 받아들이고 있었습니다. 그 결과 NetworkProcess sandbox 내 파일을 file://로 읽어올 수 있었고, 위조된 firstPartyForCookies를 통해 credentialed cross-origin body read도 가능했습니다. 이번 수정으로 URL은 HTTP(S)로 제한되었고, allowsFirstPartyForCookies 검사도 강제되어 cookie를 다루는 다른 모든 IPC entry point와 동일한 기준을 갖추게 되었습니다.
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
Source/WebKit/UIProcess/WebPageProxy.cpp
LayoutTests/ipc/load-image-for-decoding-file-url.html
Patch Details
NetworkProcess 쪽에서는 NetworkConnectionToWebProcess::loadImageForDecoding의 기존 MESSAGE_CHECK_COMPLETION(url.isValid(), ...)에 url.protocolIsInHTTPFamily() 조건이 추가되었습니다. 여기에 더해, m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) == NetworkProcess::AllowCookieAccess::Allow를 요구하는 두 번째 MESSAGE_CHECK_COMPLETION이 새로 삽입되었습니다. UIProcess 쪽에서는 WebPageProxy::loadAndDecodeImage의 early-return guard에 || !request.url().protocolIsInHTTPFamily()가 추가되었습니다. Regression test는 IPC testing API를 통해 file:///private/etc/hosts URL을 가진 ResourceRequest를 직접 조작하여 전송하고, 이제 protocolIsInHTTPFamily에서 message가 거부되는지 확인합니다.
IPC 경계에서 attacker-controlled ResourceRequest 필드를 신뢰하면서, 전송 측 process에 대한 scheme 및 cookie-access 인가를 강제하지 않은 패턴입니다.
Background
NetworkProcess는 WebContent로부터 loadImageForDecoding과 같은 IPC endpoint를 통해서만 도달 가능합니다. 이 endpoint는 sandbox化된 WebContent process를 대신해 이미지를 가져오고 decode하는 privileged endpoint입니다. ResourceRequest는 URL, firstPartyForCookies, method, header, cookie policy 등 load 관련 파라미터를 담은 완전히 serialize 가능한 묶음이며, WebContent process가 이를 구성해 IPC로 전송합니다. MESSAGE_CHECK와 그 completion 변형은 수신된 message에 대한 assertion을 검증하는 역할을 합니다. 검증에 실패하면 연결을 종료하거나 flag를 남기고 진행하지 않으므로, 이는 권한이 낮은 process로부터 도착한 message에 대한 표준 인가 게이트에 해당합니다. protocolIsInHTTPFamily()는 http/https URL에 대해서만 true를 반환하는 predicate입니다. firstPartyForCookies는 어떤 cookie를 첨부할지 결정할 때 first-party/top-site context로 사용되는 URL이며, allowsFirstPartyForCookies(processID, url)는 특정 web process가 특정 URL을 그 first-party로 사용할 권한이 있는지 확인하는 NetworkProcess의 policy check입니다. Regression test에서 사용된 IPC testing API는 페이지가 raw IPC message를 직접 합성할 수 있게 해주며, 이를 통해 compromised WebContent process 상황을 모델링합니다.
Analysis
이번 사안은 memory-safety bug가 아니라 IPC input-validation / access-control bypass, 즉 confused-deputy 패턴에 해당합니다. Fix 이전에는 loadImageForDecoding이 완전히 attacker-controlled된 ResourceRequest를 받아들이면서 url.isValid()만 검증하고 있었습니다. 여기서 빠져 있던 invariant는 두 가지입니다. 먼저, request URL이 HTTP(S) family로 제한되지 않아 file:// URL도 검증을 통과했고, 이후 network session 쪽으로 load 요청이 전달될 수 있었습니다. 다음으로, request.firstPartyForCookies()가 allowsFirstPartyForCookies와 대조되어 검증된 적이 없었기 때문에, 호출자가 임의의 first-party URL을 넘기면 loader가 cookie 첨부 여부를 결정할 때 이를 그대로 신뢰하는 구조였습니다. 즉 NetworkProcess는 request 필드를 신뢰하면서도, 이를 전송 측 process의 identity에 대해 다시 도출하거나 재인가하는 과정을 거치지 않았습니다.
Exploit 방향은 regression test를 따라가면 드러납니다. Compromised WebContent process에서 m_url이 file:///path/to/target인 ResourceRequest를 serialize한 뒤 NetworkConnectionToWebProcess_LoadImageForDecoding을 전송하는 경우를 생각해볼 수 있습니다. 패치 이전에는 url.isValid()가 통과하고 scheme도 entry point에서 거부되지 않았으므로, 이 요청이 network session까지 진행되었을 가능성이 있습니다. 다른 경로로는, m_url은 http(s)로 유지하되 m_firstPartyForCookies에 victim origin을 지정하는 방식도 있습니다. 패치 이전에는 allowsFirstPartyForCookies가 호출되지 않았으므로 이 값 역시 그대로 신뢰되었을 것입니다. 이 필드들이 실제로 local file을 읽어 image byte로 반환하는 결과로 이어지는지, 아니면 victim의 cookie가 첨부되어 body가 반환되는 결과로 이어지는지는 이 diff에 나타나지 않는 loader 경로에 달려 있습니다. 이 취약점은 second-stage bug에 해당합니다. 즉 권한 없는 web content만으로는 도달할 수 없으며, 그 자체로 NetworkProcess 내 code execution을 부여하지도 않습니다.
이 취약점은 WebContent/NetworkProcess 사이의 IPC trust boundary와 cookie first-party 인가 모델을 약화시킵니다. Security model은 compromised WebContent process가 NetworkProcess로 하여금 임의 scheme URL을 load하게 하거나 임의의 cookie first-party를 주장하게 만들 수 없다고 가정하고 있습니다. 그런데 fix 이전의 loadImageForDecoding은 그러한 인가 없이 attacker-supplied request 필드를 그대로 재전달하고 있었습니다. 이미 WebContent에서 code execution을 확보한 attacker라면 NetworkProcess를 경유해 file:// load에 도달하고 위조된 firstPartyForCookies를 넘기는 것이 이론적으로 가능합니다. 만약 이 필드들이 실제로 loader까지 그대로 흘러간다면, local file-disclosure 및 cross-origin credentialed data-read primitive로 이어질 가능성이 있습니다.
여기서 반복적으로 확인되는 교훈은, 전체 ResourceRequest를 받아 loader로 전달하는 IPC endpoint는 구조적으로 confused-deputy에 취약하다는 점입니다. 호출자가 통제하는 필드(URL scheme, firstPartyForCookies, cookie policy)는 반드시 전송자의 identity에 대해 재인가되어야 합니다. NetworkProcess의 ambient authority가 전송자의 authority보다 크기 때문입니다. url.isValid()만으로 이루어진 게이트는 문법은 검증하지만 권한은 검증하지 못합니다.
Note: Downstream loading과 cookie-attachment 관련 결과는 필드 이름과 fix가 추가한 검사의 형태로부터 추론한 내용이며, 이 필드들을 실제로 소비하는 network-session 코드는 이번 diff에 포함되어 있지 않습니다. Entry-point의 검증 공백 자체는 diff에서 직접 확인됩니다.
Audit directions
- 전체
WebCore::ResourceRequest를 받아 loader로 전달하면서url.isValid()만 검증하는 NetworkProcess IPC handler. 이러한 handler마다protocolIsInHTTPFamily()/scheme 제한 누락과allowsFirstPartyForCookies검사 누락 여부를 점검해야 합니다.Source/WebKit/NetworkProcess에서MESSAGE_CHECK*(url.isValid()패턴과WebCore::ResourceRequest&&를 인자로 받는 메서드를 검색한 뒤, 이미allowsFirstPartyForCookies를 호출하고 있는 대상과 비교해야 합니다. - Caller가 공급하는 firstPartyForCookies를 통한 confused-deputy 가능성. Cookie를 request에 첨부하는 privileged code path가 IPC로 전달된 값을 그대로 신뢰하지 않고,
firstPartyForCookies를 직접 도출하거나 인가하는지 확인해야 합니다.NetworkConnectionToWebProcess.cpp내allowsFirstPartyForCookies호출자 전체를,request.firstPartyForCookies()를 읽는 모든 loader entry point와 교차 확인하는 방식으로 시작할 수 있습니다. file://를 비롯한 non-HTTP scheme이 전송자보다 큰 authority를 가진 process에 도달하는 경로. 호출자가 제공한 URL을 load하는 UIProcess/NetworkProcess entry point(이미지 decode, prefetch, preconnect, data task)마다 dispatch 이전에 scheme을 제한하고 있는지 점검해야 합니다.WebPageProxy.cpp와NetworkConnectionToWebProcess.cpp의 loader 메서드 전반에서protocolIsInHTTPFamily사용 공백을 검색하는 것이 출발점이 될 수 있습니다.- UIProcess 쪽 mirror인
WebPageProxy::loadAndDecodeImage와 NetworkProcess handler 사이의 parity를 확인해야 합니다. Diff에서 UIProcess 쪽은 scheme 검사만 추가되었을 뿐, 명시적인 cookie 인가 검사는 받지 않았습니다. 해당 경로에서 cookie 인가가 실제로 불필요한지, 아니면 추가되어야 하는지 확인이 필요합니다.