필수 보안 헤더 설정 가이드(CSP·HSTS·X-Frame-Options)
꼭 설정해야 할 보안 헤더와 각 헤더의 역할·권장 값·흔한 실수.
HTTPS만 켜면 사이트가 안전하다고 생각하기 쉽지만, 실제로는 응답 헤더 몇 줄을 추가해야 비로소 클릭재킹· 크로스사이트 스크립팅(XSS)·MIME 스니핑·프로토콜 다운그레이드 같은 흔한 공격을 막을 수 있습니다. 이런 역할을 하는 것이 HTTP 보안 헤더입니다. 서버가 페이지를 내려줄 때 함께 보내는 지시문으로, 브라우저에게 “이 페이지는 이렇게 다뤄라”라고 알려 줍니다.
이 가이드는 실무에서 꼭 설정해야 할 핵심 보안 헤더 6종을 하나씩 짚고, 각 헤더가 무엇을 막는지, 권장 값은 무엇인지, 그리고 가장 흔한 실수까지 정리합니다. 마지막에는 현재 사이트에 어떤 헤더가 빠져 있는지 확인하는 방법도 안내합니다. 한 번 제대로 설정해 두면 보안 등급 평가에서 곧바로 점수가 오릅니다.
Content-Security-Policy (CSP) — XSS의 1차 방어선
CSP는 페이지가 어떤 출처의 스크립트·스타일·이미지·폰트를 불러올 수 있는지를 화이트리스트로 제한합니다. 공격자가 본문에 악성 <script>를 주입하더라도, 그 출처가 정책에 없으면 브라우저가 실행 자체를 거부합니다. 즉 XSS 취약점이 있더라도 실제 피해로 이어지는 것을 한 단계 더 막아 주는 가장 강력한 헤더입니다. 인라인 스크립트·인라인 스타일·eval() 차단, 폼 전송 대상 제한 (form-action), iframe 삽입 제한(frame-ancestors)까지 한 헤더로 통제할 수 있습니다.
다만 CSP는 가장 강력한 만큼 가장 깨지기 쉬운 헤더이기도 합니다. 정책이 너무 좁으면 정상 스크립트까지 막혀 사이트가 멈추고, 너무 넓으면 보안 효과가 사라집니다. 처음에는Content-Security-Policy-Report-Only로 보고만 받으며 위반 항목을 관찰한 뒤, 정책을 다듬어 정식 적용하는 것이 안전합니다. 사이트 구조에 맞는 정책 초안은 CSP 생성기로 만들어 시작하면 시행착오를 크게 줄일 수 있습니다.
Strict-Transport-Security (HSTS) — HTTPS 강제
사용자가 http://로 접속하거나 주소창에 프로토콜 없이 도메인만 입력하면, 첫 요청은 평문 HTTP로 나갑니다. 이 짧은 순간을 노려 중간자(MITM)가 트래픽을 가로채 다운그레이드시킬 수 있습니다. HSTS는 한 번 방문한 브라우저에게 “앞으로 이 도메인은 무조건 HTTPS로만 접속하라”고 기억시켜 그 틈을 없앱니다.
max-age: 이 지시를 기억할 기간(초). 권장 값은 2년인63072000입니다.includeSubDomains: 모든 하위 도메인에도 적용. 하위 도메인이 전부 HTTPS일 때만 켜세요.preload: 브라우저 내장 목록(HSTS preload list)에 등록해 첫 방문조차 HTTP를 거치지 않게 함. 등록은 사실상 되돌리기 어려우니 신중히.
권장 값: Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. 단, 아직 HTTPS로 못 옮긴 하위 도메인이 하나라도 있다면 includeSubDomains는 빼야 그 도메인이 접속 불능이 되는 사고를 막을 수 있습니다.
클릭재킹·MIME·참조자·권한 헤더
나머지 헤더들은 한 줄씩이면 끝나지만 효과는 분명합니다.
- X-Frame-Options / frame-ancestors (클릭재킹 방어): 공격자가 내 페이지를 투명한
<iframe>에 얹어 사용자가 보이지 않는 버튼을 누르게 하는 공격을 막습니다.X-Frame-Options: DENY로 모든 iframe 삽입을 차단하거나, CSP의frame-ancestors 'self'로 같은 출처만 허용합니다. CSP의frame-ancestors가 최신·우선 표준이며, 구형 브라우저 호환을 위해 둘 다 두는 경우가 많습니다. - X-Content-Type-Options: nosniff (MIME 스니핑 차단): 브라우저가 응답의
Content-Type을 무시하고 내용을 보고 타입을 추측하는 동작을 끕니다. 텍스트 파일이 스크립트로 둔갑해 실행되는 공격을 막습니다. 값은nosniff하나뿐입니다. - Referrer-Policy (참조자 정보 누출 방지): 사용자가 외부 링크로 이동할 때 어디서 왔는지(전체 URL)가 상대 사이트로 새는 것을 제어합니다. 권장 값은
strict-origin-when-cross-origin으로, 같은 출처에는 전체 경로를, 외부에는 도메인만 보냅니다. - Permissions-Policy (브라우저 기능 제한): 카메라·마이크·위치·결제 등 강력한 브라우저 기능을 페이지(및 삽입된 iframe)가 쓰지 못하게 잠급니다. 쓰지 않는 기능은 모두 꺼 두는 것이 안전합니다. 예:
Permissions-Policy: geolocation=(), camera=(), microphone=().
헤더 한눈에 보기: 역할과 권장 값
| 헤더 | 막는 것 | 권장 값 |
|---|---|---|
Content-Security-Policy | XSS, 데이터 주입, 무단 iframe | default-src 'self'; object-src 'none'; frame-ancestors 'self' (사이트별 조정) |
Strict-Transport-Security | 프로토콜 다운그레이드, 평문 첫 요청 | max-age=63072000; includeSubDomains; preload |
X-Frame-Options | 클릭재킹(iframe 삽입) | DENY 또는 SAMEORIGIN |
X-Content-Type-Options | MIME 스니핑 | nosniff |
Referrer-Policy | 참조자 URL 누출 | strict-origin-when-cross-origin |
Permissions-Policy | 불필요한 브라우저 기능 접근 | geolocation=(), camera=(), microphone=() |
확인 방법과 흔한 실수
설정했다고 끝이 아닙니다. 실제로 응답에 헤더가 실려 나가는지, 오타나 잘못된 값은 없는지 검증해야 합니다. 가장 빠른 길은 보안 헤더 검사로 도메인을 넣고 어떤 헤더가 있고 빠졌는지 한눈에 확인하는 것입니다. 브라우저 개발자 도구의 Network 탭에서 응답 헤더를 직접 보거나, 터미널에서 curl -I https://example.com으로 확인할 수도 있습니다.
현장에서 가장 자주 보이는 실수는 다음과 같습니다.
- 너무 넓은 CSP:
script-src 'unsafe-inline'이나'unsafe-eval', 와일드카드*를 넣으면 헤더는 있어도 XSS 방어 효과가 사실상 사라집니다. CSP가 깨지는 게 무서워 인라인을 통째로 허용하기보다, nonce나 해시 방식으로 필요한 인라인만 허용하세요. - HSTS에 무턱대고
includeSubDomains·preload: HTTPS가 안 되는 하위 도메인이 한 곳이라도 있으면 접속 불능이 됩니다. preload는 해제가 매우 느리니 전 도메인 HTTPS를 확인한 뒤에만 켜세요. - HTTP 응답에만 설정하고 HTTPS에는 누락: 리버스 프록시·로드밸런서 설정이 분리돼 있어 한쪽에만 헤더가 붙는 경우가 흔합니다. 양쪽 모두 확인하세요.
- 오타·잘못된 지시어:
X-Frame-Option(s 누락)처럼 철자가 틀리면 브라우저가 그냥 무시합니다. 적용한 뒤 반드시 검사 도구로 실제 반영을 확인하세요.