- Chrome의 MV3 업데이트는 기존 애드블로커의 기능을 약화시키기 위해 webRequestBlocking 권한을 제거함
- 필자는 MV3 환경에서도 webRequestBlocking을 우회할 수 있는 버그를 2023년에 발견함
- 이 버그는 JavaScript 바인딩의 허술한 구조와 옛날 코드가 그대로 남아 있었기 때문에 발생함
-
WebView 인스턴스 ID를 조작해 권한 체크를 우회함으로써 MV3 환경에서도 블로킹 기능을 사용할 수 있었음
- 현재는 패치가 적용되어 더 이상 이 우회 방법이 작동하지 않음
MV3와 애드블로커 변화
- Chrome은 MV2 익스텐션을 단계적으로 폐지하고, 대신 MV3로 전환을 진행함
- MV3는 webRequestBlocking 권한을 제거하여, 애드블로커가 네트워크 요청을 스크립트로 동적으로 차단하는 것을 막음
- 해당 권한이 빠진 대신 declarativeNetRequest API가 추가됐지만, 같은 수준의 유연성을 지원하지 않음
- 이 변화로 인해 애드블로커의 성능이 대폭 낮아지는 현상이 나타남
JavaScript 바인딩 구조의 한계
- Chrome은 코어는 C++ 로 개발되어 있지만, 익스텐션은 JavaScript로 동작하며, 확장 API도 JS 바인딩을 통해 접근함
- 2015~2016년까지는 사이트에 JS 파일(익스텐션 바인딩 모듈)을 삽입해 API를 초기화하고 검증함
- 이 방식은 JS 전역 함수·프로토타입 오버라이딩에 취약하여 Universal XSS 버그 여러 건이 발생함
- 이후 Google은 주요 바인딩을 C++로 이전했으나, 일부 JS 바인딩 파일은 여전히 남아 있음
- 아직까지도 chrome.webRequest와 같은 특정 API는 JS 바인딩 구조를 사용하고 있음
웹 요청 이벤트 클래스를 활용한 우회
-
MV2에서는 웹 요청 차단이 아래 코드로 구현 가능했음
chrome.webRequest.onBeforeRequest.addListener(() => { return { cancel: true } }, { urls: ['*://*.example.com/*'] }, ['blocking'])
-
MV3에선 blocking 옵션이 금지되어 정상적 차단이 불가능함
-
하지만 webRequest 이벤트의 .constructor를 통해 임의의 이벤트 객체를 생성할 수 있음
-
내부적으로는 JS 바인딩의 특수 래퍼 클래스가 이 이벤트 객체를 관리함
-
생성자 파라미터 중 하나인 opt_webViewInstanceId를 지정하면, 플랫폼 앱 전용 허용 로직을 우회하여 블로킹 권한 체크를 뛰어넘을 수 있음
let WebRequestEvent = chrome.webRequest.onBeforeRequest.constructor
let fakeEvent = new WebRequestEvent("webRequest.onBeforeRequest", 0, 0, 0, 1337)
fakeEvent.addListener(() => { return { cancel: true } }, { urls: ['*://*.example.com/*'] }, ['blocking'])
-
원래 플랫폼 앱만 사용할 수 있도록 설계되었지만, WebView ID 검증이 미흡하여 일반 익스텐션에서 악용 가능했음
결과 및 보안 패치
- 이 취약점으로 MV3 환경에서도 완벽한 애드블로커 개발이 실제로 가능했음
- 필자는 해당 버그를 2023년 Google에 보고했고, Chrome 118에서 WebView 권한 소유 여부를 제대로 확인하는 방식으로 패치됨
- 보상금은 지급되지 않았으며, 이는 추가 데이터 노출 없이 권한 우회만 가능했던 구조적 특성 때문임
- 본 사례는 수십 줄의 코드 수정이 거대 기업의 보안 업데이트를 무력화할 수 있음을 보여줌
결론 및 참고
- 버그는 현재 패치되어 더 이상 작동하지 않음
- 유사하게 흥미로운 Chrome 익스텐션 관련 취약점 사례로, 실제로 CVE 번호와 $10,000 보상을 받은 이슈도 존재함 (별도 블로그 글 참조)