← All issues

[3] Connections for Private Click Measurement are not proxied

Severity: High | Component: WebKit Private Click Measurement (Network Process) | 975a11f

PCM attribution report 전송 및 token fetch가 발생할 때마다 사용자의 실제 IP 주소가 완전히 노출됩니다. 이는 PCM이 구조적으로 의존하는 privacy proxy를 직접 우회하는 동작에 해당합니다. diff에서 드러나는 _sourceApplicationBundleIdentifier 누락과 cross-site classification 부재를 근거로 confidence 0.93으로 High로 평가하였습니다. 다만 Apple network stack의 정확한 proxy routing 동작은 domain knowledge에서 추론된 것으로, diff만으로는 완전히 검증되지 않습니다.

PCM attribution report와 fraud prevention token fetch는 Apple network stack이 요구하는 두 가지 신호가 없어서 privacy proxy를 통해 전달되지 않았습니다. 첫 번째는 application bundle(예: Safari)과의 연결이고, 두 번째는 third-party 리소스 요청으로의 분류입니다. 이번 수정에서는 application bundle identifier 또는 audit token을 전달 경로에 연결하고, cross-site mainDocumentURL을 임의로 생성하여 설정했습니다. 추가 안전장치로 _setPrivacyProxyFailClosed:YES도 설정되어, proxy를 사용할 수 없는 상황에서 직접 연결로 fallback하는 대신 요청이 실패하도록 변경되었습니다.

Source/WebKit/NetworkProcess/PrivateClickMeasurement/cocoa/PrivateClickMeasurementNetworkLoaderCocoa.mm

- static NSURLSession *statelessSessionWithoutRedirectsSingleton()
+ static NSURLSession *statelessSessionWithoutRedirectsSingleton(const ApplicationBundleIdentifierOrAuditToken& applicationBundleIdentifier)
{
...
configuration.get()._shouldSkipPreferredClientCertificateLookup = YES;
+
+ WTF::switchOn(applicationBundleIdentifier,
+ [&] (const String& bundleIdentifier) {
+ configuration.get()._sourceApplicationBundleIdentifier = bundleIdentifier.createNSString().get();
+ }, [&] (const Vector<uint8_t>& auditToken) {
+ configuration.get()._sourceApplicationAuditTokenData = [NSData dataWithBytes:auditToken.span().data() length:auditToken.size()];
+ }
+ );
+
...
auto request = adoptNS([[NSMutableURLRequest alloc] initWithURL:url.createNSURL().get()]);
+ [request _setPrivacyProxyFailClosed:YES];
[request setValue:WebCore::HTTPHeaderValues::maxAge0().createNSString().get() forHTTPHeaderField:@"Cache-Control"];
[request setValue:WebCore::standardUserAgentWithApplicationName({ }).createNSString().get() forHTTPHeaderField:@"User-Agent"];
+ RetainPtr crossSiteMainDocument = [NSURLComponents componentsWithURL:request.get().URL resolvingAgainstBaseURL:NO];
+ crossSiteMainDocument.get().host = [NSString stringWithFormat:@"not-%@", crossSiteMainDocument.get().host];
+ [request setMainDocumentURL:crossSiteMainDocument.get().URL];
- RetainPtr task = [statelessSessionWithoutRedirectsSingleton() dataTaskWithRequest:request.get() ...];
+ RetainPtr task = [statelessSessionWithoutRedirectsSingleton(applicationBundleIdentifier) dataTaskWithRequest:request.get() ...];

이 패치는 ApplicationBundleIdentifierOrAuditToken(Variant<String, Vector<uint8_t>>)을 NetworkSession에서 PrivateClickMeasurementManager를 거쳐 모든 PCM::NetworkLoader::start() 호출까지 전달합니다. Cocoa 측에서는 statelessSessionWithoutRedirectsSingleton()이 이제 사용 가능한 값에 따라 _sourceApplicationBundleIdentifier 또는 _sourceApplicationAuditTokenDataNSURLSessionConfiguration에 설정합니다. 요청 자체에는 _setPrivacyProxyFailClosed:YES가 설정되고, 요청 host 앞에 not-을 붙여 cross-site mainDocumentURL을 임의 생성함으로써 URL loading system이 해당 요청을 third-party subresource로 분류하도록 강제합니다. daemon 진입점(PCMDaemonEntryPoint.mm)에서는 daemon code path에 Safari bundle identifier를 직접 하드코딩했습니다.

Privacy에 민감한 네트워크 요청에서 application identity와 cross-site classification이 누락되어 proxy bypass가 발생한 패턴.

Private Click Measurement(PCM)는 WebKit의 privacy를 보존하는 광고 attribution 시스템입니다. 사용자가 source 사이트의 광고를 클릭하고 이후 destination 사이트에서 전환이 발생하면, PCM은 destination으로 attribution report를 전송합니다. 이 report는 사용자의 IP 주소를 숨기는 privacy proxy를 통해 전송되도록 설계되어 있습니다. 또한 PCM은 서버에서 fraud prevention token key material을 가져오는데, 이 역시 동일하게 proxy를 통해 전달되어야 합니다.

Apple network stack은 연결을 proxy로 전달할지 판단할 때 두 가지 신호를 사용합니다. 하나는 source application identity(NSURLSessionConfiguration_sourceApplicationBundleIdentifier 또는 _sourceApplicationAuditTokenData)로, 요청을 Safari와 같은 알려진 앱과 연결합니다. 다른 하나는 해당 요청이 third-party(cross-site) subresource인지 여부로, 요청 URL의 host와 mainDocumentURL의 host를 비교해 판단합니다. proxy routing이 활성화되려면 두 신호 모두 필요합니다. NSMutableURLRequest에 설정하는 _setPrivacyProxyFailClosed:YES 지시자는 proxy에 연결할 수 없는 경우 직접 연결로 fallback하지 않고 요청이 실패하도록 보장합니다.

수정 이전에는 PCM 네트워크 요청이 proxy routing에 필요한 두 신호 중 어느 것도 포함하지 않은 채 전송되었습니다. NSURLSessionConfiguration에는 source application identifier가 없었고, 요청에는 cross-site classification을 발생시킬 mainDocumentURL도 설정되지 않았습니다. 두 신호가 모두 없는 상태에서 Apple network proxy 인프라는 해당 요청을 일반적인 first-party 요청으로 처리하여 직접 전송했을 가능성이 있습니다. 다만 이는 routing 로직에 대한 domain knowledge에서 추론된 것으로, diff만으로는 확인되지 않습니다. fail-closed 동작 역시 부재했습니다. proxy를 사용할 수 없는 상황이라면 요청이 실패하는 대신 직접 연결로 자동 전환되었을 것입니다.

cross-site classification을 강제하는 방식도 구조적으로 주목할 만합니다. 요청 host 앞에 not-을 붙여 mainDocumentURL을 임의 생성합니다. 예를 들어 example.com으로 향하는 요청에는 not-example.com을 사용합니다. 이렇게 하면 host mismatch가 반드시 발생하여 third-party classification이 활성화됩니다. PCM 요청은 page load가 아닌 Network Process에서 시작되기 때문에 자연스러운 "first-party" document context가 없습니다. 이 방식은 그런 상황에 대한 실용적인 우회책에 해당합니다.

🔒

Explores the privacy model implications and the specific network-level signals that were missing, along with the fail-closed defense-in-depth measure

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

🔒

Multiple audit patterns identified for proxy bypass variants across WebKit's network subsystems, with concrete search targets

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