본문 바로가기
MAKESITE
무료 스캔 문의하기 로그인

점검 항목 · 보안

클릭재킹 방어 헤더 미설정

우리 서버에는 아무 흔적도 남지 않는 공격입니다. 코드에 취약점이 하나도 없어도 성립하고, 그래서 응답 헤더 말고는 막을 방법이 없습니다.

심각도 보통 보안 무료 스캔 판정 항목 /check/x-frame

01DEFINITION

한 줄로 말하면

공격자가 자기 페이지에 우리 사이트를 투명한 iframe 으로 얹고 그 위에 가짜 버튼을 겹칩니다. 방문자는 자기가 무엇을 누르는지 모른 채, 로그인된 상태로 우리 사이트에서 어떤 동작을 실행하게 됩니다. 이것이 클릭재킹입니다. X-Frame-Options 한 줄이면 막히고, 요즘은 CSP 의 frame-ancestors 로 어느 도메인까지 허용할지 더 정밀하게 정합니다.

02RISK

없으면 무슨 일이 생기나

공격자는 그럴듯한 이벤트 페이지를 하나 만듭니다. 화면에는 응모하기 버튼이 있고, 그 버튼 바로 위에 우리 사이트가 투명한 창으로 겹쳐 있습니다. 방문자가 응모하기를 누르는 순간 실제로 눌리는 것은 우리 사이트의 어떤 버튼입니다. 방문자는 이미 우리 사이트에 로그인해 있으므로 그 동작은 정상적으로 처리됩니다.

무엇이 눌리느냐가 피해의 크기를 정합니다. 회원 탈퇴, 권한 위임 동의, 결제 승인, 게시글 삭제처럼 한 번 누르면 되는 동작이 표적이 됩니다. 우리 서버의 로그에는 정상 로그인 사용자가 정상적으로 요청한 것으로 남기 때문에, 사후에 무엇이 잘못됐는지 가려내기도 어렵습니다.

방문자 입장에서 방어할 방법이 사실상 없다는 점도 문제입니다. 주소를 확인해도 자기가 보고 있는 것은 공격자의 페이지이고, 우리 사이트는 눈에 보이지 않는 층에 있습니다. 이 항목이 서버 설정 한 줄인데도 심각하게 다뤄지는 이유입니다.

03CHECK IT YOURSELF

직접 확인하는 방법

아래는 저희를 거치지 않고 지금 해 보실 수 있는 방법입니다. 결과를 저희에게 보내실 필요도 없습니다.

  1. 01
    두 헤더 중 하나라도 있는지 본다

    X-Frame-Options 와 CSP 의 frame-ancestors 는 같은 일을 합니다. 둘 다 없으면 이 항목에 걸립니다. 최신 브라우저는 둘 다 있을 경우 frame-ancestors 를 따릅니다.

    $ curl -sI https://example.com/ | grep -iE "x-frame-options|content-security-policy"
    x-frame-options: SAMEORIGIN
  2. 02
    직접 iframe 에 넣어 본다

    로컬에 아래 내용으로 파일 하나를 만들어 브라우저로 엽니다. 우리 사이트가 그려지면 막혀 있지 않은 것이고, 빈 화면이나 차단 메시지가 나오면 정상입니다. 개발자도구 콘솔에도 차단 사유가 남습니다.

    <iframe src="https://example.com/" width="600" height="400"></iframe>
  3. 03
    홈이 아니라 동작이 있는 페이지로 본다

    표적이 되는 것은 버튼이 있는 페이지입니다. 회원정보 수정, 주문 확인, 관리자 화면처럼 한 번의 클릭으로 무언가 바뀌는 주소를 확인하십시오. 홈에만 헤더가 붙어 있는 설정이 있습니다.

04HOW TO FIX

고치는 방법

서버 환경에 따라 적을 자리가 다릅니다. 아래는 가장 흔한 구성 기준이고, 적용 전에 현재 설정을 백업하십시오.

같은 도메인 안에서만 창을 허용한다

대부분의 사이트에 맞는 설정입니다. 우리 페이지가 우리 페이지 안에서 창으로 열리는 것은 허용하고, 외부 도메인에서는 막습니다.

Header always set X-Frame-Options "SAMEORIGIN"
CSP 를 쓴다면 frame-ancestors 로 적는다

이쪽이 최신 방식이고 허용 도메인을 정밀하게 지정할 수 있습니다. 창을 아예 허용하지 않으려면 none 으로 둡니다.

Header always set Content-Security-Policy "frame-ancestors 'self'"
# 특정 파트너만 허용하는 경우
# frame-ancestors 'self' https://partner.example.com
창으로 열려야 하는 페이지는 따로 판단한다

결제 모듈, 지도, 외부 위젯으로 제공하는 페이지는 다른 사이트 안에서 열려야 정상입니다. 이런 경로는 전역 정책에서 빼고 별도 헤더를 적용하십시오. 전부 막았다가 결제가 안 되는 사고가 이 항목에서 나옵니다.

<Location "/widget/">
    Header always unset X-Frame-Options
    Header always set Content-Security-Policy "frame-ancestors https://partner.example.com"
</Location>
주의. X-Frame-Options 에 특정 도메인을 지정하는 ALLOW-FROM 문법은 대부분의 브라우저가 더 이상 지원하지 않습니다. 파트너 도메인만 허용해야 한다면 CSP 의 frame-ancestors 를 쓰십시오.

05FAQ

자주 묻는 질문

우리 사이트는 로그인 기능이 없는데도 필요한가요

위험은 줄지만 0 은 아닙니다. 로그인이 없어도 문의 폼 제출, 설문 응답, 외부 링크 유도처럼 한 번의 클릭으로 일어나는 동작은 그대로 남습니다. 또 우리 페이지를 그대로 감싸 자기 콘텐츠처럼 보여 주는 것 자체를 막는 효과도 있습니다.

SAMEORIGIN 과 DENY 중 무엇을 써야 하나요

사이트 안에서 iframe 을 전혀 쓰지 않는다면 DENY 가 더 안전합니다. 다만 게시판 에디터의 미리보기, 관리자 화면의 일부 기능이 내부적으로 iframe 을 쓰는 경우가 있어 확인이 필요합니다. 확신이 없으면 SAMEORIGIN 으로 시작하십시오.

X-Frame-Options 와 CSP 를 둘 다 넣어도 되나요

둘 다 넣어도 충돌하지 않습니다. 최신 브라우저는 frame-ancestors 를 우선하고, 지원하지 않는 옛 브라우저는 X-Frame-Options 를 따릅니다. 두 값이 서로 다른 정책을 말하지 않게만 맞춰 두시면 됩니다.

이 헤더를 켜면 네이버나 카카오 공유에 영향이 있나요

없습니다. 공유 미리보기는 페이지를 창으로 여는 것이 아니라 서버가 직접 읽어 OG 태그를 뽑는 방식이라 이 헤더의 영향을 받지 않습니다. 영향을 받는 것은 다른 사이트가 우리 페이지를 화면 안에 끼워 넣어 보여 주는 경우뿐입니다.
여기까지는 항목 일반론입니다. 무엇이고 왜 문제이며 어떻게 고치는지는 감출 이유가 없어 그대로 적었습니다. 리포트에 들어가는 것은 귀사 사이트에서 실제로 어떤 값이 나왔는지 — 어느 주소의 어느 응답인지, 그 환경에서 어떤 순서로 적용해야 하는지, 예상 공수가 얼마인지입니다. 이 페이지만 보고 직접 고치셔도 되고, 실제로 그러시라고 적어 두었습니다.

GET STARTED

내 사이트는
이 항목을 통과할까요

주소만 넣으시면 이 항목을 포함해 25가지를 판정해 점수와 실패 개수를 보여 드립니다. 로그인도, 소유 확인도 필요 없습니다 — 전부 밖에서 관찰만 하는 항목이기 때문입니다.