← All issues

[9] Don't log interaction from temporary gesture on storage access rejection

requestStorageAccess()'s rejection path silently logged user interaction — ITP's opener heuristic read that as consent and granted access without a prompt.

Severity: High | Component: WebCore DOM (Storage Access API) | 5021f99

gesture 없이 호출된 requestStorageAccess() rejection path에서 사용자 상호작용이 암묵적으로 기록되는 경로를 수정합니다. 기록된 상호작용을 ITP opener 휴리스틱이 동의로 해석하면, 프롬프트 없이 cross-site 쿠키 접근이 허용됩니다. High 분류 이유입니다.

enableTemporaryTimeUserGesture()는 기본값인 ProcessInteractionStyle::ImmediateUserGestureIndicator를 생성합니다. 이 과정에서 합성 사용자 상호작용이 side effect로 기록됩니다. 이 함수는 rejection path에서 호출자가 window.open()을 계속 사용할 수 있도록 하는 용도로 쓰였습니다. 다만 rejection path에서 조작된 상호작용이 생성되면서 requestStorageAccessUnderOpener()가 유발되는 문제로 이어졌습니다.

Source/WebCore/dom/DocumentStorageAccess.cpp

- m_temporaryUserGesture = makeUnique<UserGestureIndicator>(IsProcessingUserGesture::Yes, protect(m_document).ptr());
+ m_temporaryUserGesture = makeUnique<UserGestureIndicator>(IsProcessingUserGesture::Yes, protect(m_document).ptr(), UserGestureType::ActivationTriggering, UserGestureIndicator::ProcessInteractionStyle::Never);

한 줄 변경으로 UserGestureType::ActivationTriggeringProcessInteractionStyle::Never를 명시적으로 전달합니다. gesture 기반 기능은 그대로 유지하면서 interaction 기록은 억제됩니다. 회귀 테스트는 cross-site iframe이 포함된 popup을 설치하며, gesture 없이 호출된 requestStorageAccess() rejection이 더 이상 storage access를 허용하지 않음을 검증합니다.

side effect가 없어야 할 용도로 사용된 side-effecting 헬퍼: 내부 제어 흐름 목적으로 생성된 임시 UserGestureIndicator가 사용자 상호작용 telemetry를 암묵적으로 방출하며, downstream privacy 휴리스틱이 이를 동의로 해석합니다.

UserGestureIndicator는 코드 실행 구간을 "사용자 gesture 처리 중" 상태로 표시하는 RAII 헬퍼로, gesture 기반 API를 활성화하는 역할을 담당합니다. 생성자는 ProcessInteractionStyle 인자를 받는데, 이 값에 따라 동작이 달라집니다. Immediate를 전달하면 페이지 도메인에 사용자 상호작용 이벤트가 등록되는 side effect가 발생해 privacy telemetry에 기록됩니다. 반면 Never를 전달하면 gesture 상태만 활성화되며 telemetry 기록은 이루어지지 않습니다. document.requestStorageAccess()는 cross-site iframe이 first-party 쿠키 접근을 요청하는 API입니다. ITP(Resource Load Statistics)는 도메인별 상호작용을 추적하며, requestStorageAccessUnderOpener()는 해당 origin에 최근 상호작용 기록이 있고 popup에 opener가 존재하면 자동으로 storage access를 허용합니다.

UserGestureIndicator는 논리적으로 분리 가능한 두 가지 역할을 수행합니다. gesture 범위를 표시하는 역할과 사용자 상호작용을 기록하는 역할입니다. 기본 생성자는 이 두 동작을 하나로 묶어두었습니다. 결과적으로 순수하게 내부 제어 흐름 목적으로만 헬퍼를 사용하던 호출자들이 의도치 않게 interaction 기록 동작까지 유발하게 된 셈입니다.

공격 시나리오의 형태는 다음과 같습니다. publisher.example에서 window.open()으로, 최근 상호작용 기록이 있거나 ITP에서 prevalent로 분류된 tracker.example을 popup으로 열면, 해당 popup은 tracker를 가리키는 cross-site iframe을 포함한 페이지를 로드합니다. iframe의 onload 핸들러가 사용자 gesture 없이 document.requestStorageAccess()를 호출하면 promise는 rejection되지만, rejection path에서 임시 gesture가 합성 상호작용을 기록하게 됩니다. 이후 opener 휴리스틱은 popup-with-opener 조건과 최근 상호작용 기록을 함께 확인하고, 프롬프트 없이 tracker origin에 대한 storage access를 허용하는 결과로 이어집니다.

이 vulnerability는 ITP가 보호하는 third-party storage access의 사용자 동의 경계를 약화시킵니다. Storage Access API의 설계 원칙은 cross-site 쿠키 접근에 명시적 프롬프트 또는 사전 상호작용이 반드시 필요하다는 것입니다. 그러나 이 버그를 통해 동의를 생성해서는 안 되는 API의 rejection path를 거쳐 해당 원칙을 우회할 수 있었습니다.