클릭재킹은 보이는 페이지 아래에 숨겨진 페이지의 버튼을 누르게 만드는 공격입니다. 작동 원리와 확실히 막는 방법을 살펴봅니다(공격 절차는 다루지 않습니다).
무엇이 표적이 되는가
'한 번 누르면 끝나고, 가치 있는' 모든 것입니다.
| 표적이 되기 쉬운 것 | 이유 |
|---|---|
| 송금·구매 확인 | 한 번의 클릭으로 돈이 움직임 |
| 설정 변경(공개 범위, 연동 앱) | 이후의 탈취나 데이터 접근을 위한 발판 |
| OAuth·권한 '허용' | 계정 연동을 조용히 승인 |
| 소셜 팔로우·좋아요·게시 | 확산과 좋아요 조작에 악용 |
왜 통하는가
브라우저는 다른 사이트를 iframe으로 불러와 위에 쌓을 수 있습니다. 공격자는 그 iframe을 투명하게(opacity 0) 만들어, 사용자는 그 아래 배치된 미끼 UI만 보게 됩니다. 사용자가 '가짜 버튼'을 누르면, 바로 위에 겹쳐진 진짜 버튼이 그 클릭을 받습니다.
CSRF와 비슷해 보이지만, CSRF는 뒤에서 요청을 보내는 반면 클릭재킹은 사용자가 진짜 화면을 직접 조작하게 합니다. 그래서 CSRF 토큰만으로는 막을 수 없습니다.
실제 사례: 브라우저 플러그인의 권한 대화상자(2008년)
2008년 10월, Adobe는 여러 브라우저에 영향을 주는 클릭재킹 문제에 관한 보안 권고(APSA08-08)를 발표했습니다. 보도에 따르면 Flash Player의 설정 대화상자를 보이지 않게 겹쳐, 사용자가 모르는 사이에 사이트에 카메라와 마이크 접근을 허용하게 만들 수 있었습니다. Adobe는 수정된 Flash Player를 배포했고, 임시 대응으로 설정 관리자에서 '항상 거부'를 선택하라고 안내했습니다.
교훈은 송금 버튼만 표적이 되는 것이 아니라는 점입니다. '허용', '동의', '연결'처럼 한 번의 클릭으로 권한을 넘기는 화면은 모두 대상입니다. MITRE는 이 약점을 CWE-1021(렌더링된 UI 레이어나 프레임의 부적절한 제한)로 분류합니다.
방어: 다른 사이트가 내 사이트를 삽입하지 못하게 한다
CSP frame-ancestors 설정(가장 중요)
Content-Security-Policy: frame-ancestors 'self'를 추가하세요(내 오리진만 프레임에 넣을 수 있음). 아무도 삽입할 필요가 없다면 'none'이 가장 안전합니다. 명시적으로 허용하는 파트너에 한해서만 도메인을 나열하세요.
X-Frame-Options도 추가(하위 호환)
구형 클라이언트를 위해 X-Frame-Options: DENY(또는 SAMEORIGIN)도 함께 보내세요. 이렇게 하면 구형·신형 브라우저 모두에 대응할 수 있습니다.
중요한 행동에는 확인 단계를 추가
송금과 권한 부여에는 재인증, 확인 대화상자, 의도 확인을 두세요. 보이지 않는 오버레이로 끝까지 진행시키기 어려워집니다.
세션 쿠키를 SameSite로
SameSite=Lax/Strict는 크로스 사이트 자동 전송을 줄입니다. 그 자체로 클릭재킹의 완전한 해결책은 아니지만, '다른 사이트에서 시작된 행동'이라는 더 넓은 범주를 약화시킵니다.
X-Frame-Options와 CSP frame-ancestors의 차이
두 헤더 모두 다른 사이트의 삽입을 막지만 세부 사항이 다릅니다.
| X-Frame-Options | CSP frame-ancestors | |
|---|---|---|
| 값 | DENY(모두 차단) / SAMEORIGIN(같은 오리진만) | 'none' / 'self' / 허용 도메인 목록 |
| 여러 곳의 삽입 허용 | 불가(ALLOW-FROM은 최신 브라우저에서 작동하지 않음) | 가능 |
| meta 태그로 설정 | 효과 없음 | 효과 없음 |
| 둘 다 있을 때 | 지원 브라우저에서는 무시됨 | 우선 적용 |
| 역할 | 구형 브라우저용 대비책 | 현재의 주된 방어 |
둘 다 보낼 때는 의미를 맞춥니다. frame-ancestors 'none'에는 DENY를, frame-ancestors 'self'에는 SAMEORIGIN을 짝짓습니다.
흔한 설정 실수
- CSP의
default-src로 충분하다고 생각한다.frame-ancestors는default-src로 대체되지 않습니다.default-src 'none'이어도frame-ancestors를 쓰지 않으면 삽입은 허용된 채로 남습니다 - meta 태그에 넣는다: 이 경우
<meta http-equiv>는 효과가 없으므로 응답 헤더로 보냅니다 Content-Security-Policy-Report-Only로 둔다: 위반을 보고만 할 뿐 삽입을 막지 않습니다. 동작을 확인했다면 차단하는 헤더로 바꿉니다- 일부 페이지에만 설정한다: 홈페이지는 확인하고 로그인 페이지, 설정 페이지, 오류 페이지는 놓치기 쉽습니다. CDN이나 프록시가 헤더를 지우지 않는지도 확인합니다
자신의 사이트 점검 방법
헤더 점검 도구로 확인한다
보안 헤더 점검에 사이트 URL을 넣고 X-Frame-Options와 CSP frame-ancestors가 반환되는지 봅니다. 홈페이지뿐 아니라 로그인 후 설정 페이지, 송금이나 권한 부여 페이지도 확인합니다.
브라우저 개발자 도구로 확인한다
네트워크 탭을 연 상태로 페이지를 새로 고치고 HTML 응답의 헤더를 봅니다. 헤더가 두 번 보내지거나 서로 다른 값으로 보내지지 않는지도 확인합니다.
한곳에서 설정한다
한곳(nginx나 Caddy 같은 웹 서버, 리버스 프록시, 프레임워크)에서 모든 응답에 추가하면 누락과 중복을 함께 막을 수 있습니다. CSP를 새로 작성한다면 CSP 빌더로 frame-ancestors를 포함한 정책을 만들 수 있습니다.
이 사이트의 관점: 헤더 한 줄이 가장 효과적인 대책이므로 먼저 내 사이트부터 점검한다
클릭재킹 방어는 복잡한 코드가 아니라 삽입을 막는 한두 줄의 응답 헤더입니다. 프레임워크나 리버스 프록시(Caddy/nginx)에서 사이트 전체에 적용해 빠지는 곳이 없게 하세요. 가장 흔한 실패는 설정했다고 생각했지만 실제로는 빠져 있는 헤더이므로, 설정한 뒤에는 위의 방법으로 실제로 반환되는지 확인하세요.
출처(1차 자료)
- OWASP, "Clickjacking Defense Cheat Sheet" — cheatsheetseries.owasp.org(frame-ancestors, X-Frame-Options, SameSite. ALLOW-FROM은 작동하지 않음, meta 태그는 효과 없음, 프레임 버스팅의 한계)
- MDN, "CSP: frame-ancestors" — developer.mozilla.org(허용 값, default-src로 대체되지 않음, meta 미지원)
- RFC 7034, "HTTP Header Field X-Frame-Options" — rfc-editor.org
- IPA, '안전한 웹사이트 만드는 법 1.9 클릭재킹'(일본어) — ipa.go.jp
- Adobe, "Security Advisory for Flash Player (APSA08-08)"(2008년 10월 7일) — adobe.com
- Computerworld, "Clickjackers could hijack webcams, microphones, Adobe warns"(2008년 10월) — computerworld.com(카메라·마이크 권한 대화상자가 표적)
- MITRE, "CWE-1021: Improper Restriction of Rendered UI Layers or Frames" — cwe.mitre.org
다음으로 읽기
- 용어집: CSRF란 · XSS란
- 가이드: 보안 헤더 고르기(아직 필요한 것과 낡은 것)
- 점검: 보안 헤더 점검 · CSP 빌더