상위: Web Security
요약
Clickjacking(클릭재킹, UI Redressing)은 투명한 iframe으로 피해자가 의도하지 않은 버튼이나 링크를 클릭하게 만드는 공격입니다. 사용자의 클릭을 "가로채서" 악성 행위를 수행합니다.
공격 원리
[보이는 화면] [실제 구조]
┌─────────────────┐ ┌─────────────────┐
│ │ │ 투명한 iframe │
│ 🎁 경품받기! │ │ (실제 은행 사이트) │
│ [클릭!] │ │ [송금 버튼] │ ← 실제 클릭됨
│ │ │ │
└─────────────────┘ └─────────────────┘
사용자 생각: "경품받기 버튼 클릭" 실제 동작: "은행 송금 버튼 클릭"
공격 코드
기본 Clickjacking
<!DOCTYPE html>
<html>
<head>
<style>
.decoy {
position: absolute;
width: 300px;
height: 100px;
z-index: 1; /* 뒤에 배치 */
}
iframe {
position: absolute;
width: 300px;
height: 100px;
opacity: 0; /* 투명하게 */
z-index: 2; /* 앞에 배치 */
}
</style>
</head>
<body>
<div class="decoy">
<button>🎁 무료 경품 받기!</button>
</div>
<iframe src="https://bank.com/transfer?to=attacker&amount=10000"></iframe>
</body>
</html>
드래그 앤 드롭 Clickjacking
<style>
#source { width: 200px; height: 50px; }
#target { position: absolute; opacity: 0; }
</style>
<div id="source" draggable="true">
이 상자를 드래그하세요!
</div>
<iframe id="target" src="https://target.com/sensitive-action"></iframe>
<script>
document.getElementById('source').ondragend = function() {
// 드롭 시 iframe 내 요소에 데이터 전달
};
</script>
커서 조작 Clickjacking
<style>
body { cursor: none; }
.fake-cursor {
position: fixed;
pointer-events: none;
/* 실제 커서 위치에서 오프셋 */
transform: translate(200px, 100px);
}
</style>
<img class="fake-cursor" src="cursor.png">
공격 시나리오
시나리오 1: 좋아요/팔로우 탈취
피해자: "이 게임 시작" 클릭
실제: Facebook "좋아요" 또는 Twitter "팔로우" 클릭
시나리오 2: 카메라/마이크 권한 탈취
피해자: "퀴즈 시작" 클릭
실제: 브라우저의 "카메라 허용" 버튼 클릭
시나리오 3: 파일 다운로드
피해자: "다음" 클릭
실제: 악성 파일 다운로드 확인 버튼 클릭
시나리오 4: 결제 승인
피해자: "무료 체험 시작" 클릭
실제: 결제 승인 버튼 클릭
방어 방법
1. X-Frame-Options 헤더 (기본)
X-Frame-Options: DENY
| 값 | 설명 |
|---|---|
DENY | 모든 프레임 내 로드 차단 |
SAMEORIGIN | 같은 도메인에서만 허용 |
ALLOW-FROM uri | 특정 도메인만 허용 (deprecated) |
서버 설정:
# Nginx
add_header X-Frame-Options "DENY" always;
# Apache
Header always set X-Frame-Options "DENY"
2. Content-Security-Policy (권장)
Content-Security-Policy: frame-ancestors 'none';
| 값 | 설명 |
|---|---|
'none' | 어디서도 프레임 불가 |
'self' | 같은 도메인만 허용 |
https://trusted.com | 특정 도메인만 허용 |
장점: X-Frame-Options보다 유연하고 현대적
3. JavaScript Frame Busting
// 기본 Frame Buster
if (top !== self) {
top.location = self.location;
}
우회 가능한 약점:
<!-- 공격자가 차단 가능 -->
<iframe src="target.com" sandbox="allow-forms"></iframe>
<!-- sandbox가 JavaScript 실행 제한 -->
더 강력한 Frame Buster:
<style>
/* 기본적으로 숨김 */
html { display: none !important; }
</style>
<script>
if (self === top) {
document.documentElement.style.display = 'block';
} else {
top.location = self.location;
}
</script>
4. SameSite 쿠키
Set-Cookie: session=abc123; SameSite=Strict; Secure
효과: iframe에서 로드 시 쿠키가 전송되지 않음 → 인증 실패
5. 중요 작업에 추가 확인
// 2단계 확인
function transfer() {
const confirmed = confirm("정말 송금하시겠습니까?\n금액: 1,000,000원");
if (confirmed) {
// 송금 실행
}
}
프레임워크별 설정
Django
# settings.py
X_FRAME_OPTIONS = 'DENY'
# 또는 특정 뷰만
from django.views.decorators.clickjacking import xframe_options_deny
@xframe_options_deny
def my_view(request):
pass
Express.js
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'deny' }));
Spring
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().deny();
}
}
ASP.NET
// Web.config
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="DENY" />
</customHeaders>
</httpProtocol>
</system.webServer>
테스트 방법
1. 수동 테스트
<!-- 테스트 페이지 -->
<iframe src="https://target.com" width="500" height="500"></iframe>
iframe에 사이트가 로드되면 취약
2. 브라우저 개발자 도구
Console 탭에서 에러 확인:
"Refused to display in a frame because X-Frame-Options is set to DENY"
3. curl로 헤더 확인
curl -I https://target.com | grep -i frame
# X-Frame-Options: DENY
# Content-Security-Policy: frame-ancestors 'none'
취약한 기능 우선순위
| 우선순위 | 기능 | 이유 |
|---|---|---|
| 최고 | 결제, 송금 | 금전적 피해 |
| 높음 | 비밀번호 변경, 계정 삭제 | 계정 탈취 |
| 중간 | 설정 변경, 권한 부여 | 보안 약화 |
| 낮음 | 좋아요, 팔로우 | 소셜 조작 |