가장 게으른 보안 조언은 모든 HTTP 헤더에 동일한 톤으로 한 줄씩 주는 거대한 체크리스트다.
이 14개 헤더를 설정하세요. 행운을. 프로덕션에서 봅시다.
이러면 X-Content-Type-Options: nosniff를 자랑스럽게 보내면서 Content Security Policy도 없고, HSTS preload도 없고, iframe 정책은 2017년 블로그에서 복사한 사이트가 나온다. 헤더는 동등하지 않다. 동등하게 심각한 문제를 풀지 않는다. 배포 비용도 같지 않다. 동등하게 취급하는 건 소화기와 화분을 둘 다 복도에 있다는 이유로 동등한 안전 장비로 취급하는 것과 같다.
수천 개 도메인의 헤더 설정을 본 뒤, 실제 순위는 이렇다.
Tier 1: 이것부터 하거나 나머지는 신경 끄거나
Strict-Transport-Security (HSTS). 브라우저가 이후 모든 요청에 HTTPS를 쓰도록 강제한다. 없으면 첫 요청이 가로채져서 HTTP로 다운그레이드될 수 있다 — 서버가 HTTPS로 리다이렉트해도. 부재가 실제 공격 가능한 표면을 만드는 몇 안 되는 헤더 중 하나다. max-age를 최소 1년으로 설정. 모든 서브도메인을 통제하면 includeSubDomains 포함. 안정적 도메인이면 preload 고려.
Content-Security-Policy (CSP). 페이지에 어떤 스크립트, 스타일, 이미지, 리소스가 로드될 수 있는지 제어한다. 제대로 설정된 CSP는 XSS에 대한 가장 효과적인 방어다. 이 리스트에서 가장 어려운 헤더이기도 하다. Tier 1에 넣는 이유는 쉬워서가 아니라 그만큼 중요해서다.
문제: 현실의 CSP 대부분이 고장 나 있다. 애플리케이션이 깨져서 unsafe-inline과 unsafe-eval을 쓰는데, 그러면 목적 전체가 무효화된다. unsafe-inline이 있는 CSP는 걸쇠 뺀 안전벨트다. CSP를 제대로 하려면 인라인 스크립트, 레거시 템플릿, 서드파티 태그 정글을 마주해야 하고, 프론트엔드 누군가가 unsafe-inline을 성격 특성처럼 쓰고 있었다는 걸 발견하게 된다. Nonce가 보통 가장 깔끔한 경로다. 해시는 안정적인 인라인 블록에 잘 작동한다.
CSP가 어려운 건 문법이 어려워서가 아니다. CSP가 어려운 건 앱의 모양을 드러내기 때문이다. 그래서 사람들이 집착하는 헤더 리스트의 절반보다 중요한 것이기도 하다.
X-Content-Type-Options: nosniff. 한 줄. 브라우저가 응답의 MIME 타입을 추측하는 걸 막는다. 없으면 브라우저가 텍스트 파일을 JavaScript로 해석해서 실행할 수 있다. 가장 쉬운 보안 헤더다 — 값 하나, 설정 없음, 깨뜨릴 위험 없음. 설정하고 넘어가자.
Tier 2: 중요하고, 시간 쓸 가치 있음
X-Frame-Options (또는 CSP frame-ancestors). 사이트가 다른 도메인의 iframe에 삽입되는 걸 막는다. 클릭재킹을 차단한다. DENY나 SAMEORIGIN. CSP가 이미 있으면 frame-ancestors 'self'가 같은 일을 더 유연하게 한다. 둘 다 필요 없다.
Referrer-Policy. 극적으로 안 들려서 잊히는 헤더. 그러다가 비밀번호 재설정 링크나 계정 경로가 Referer 헤더를 통해 서드파티 분석 엔드포인트로 유출되면 브라우저가 기본적으로 과하게 공유한다는 걸 다들 기억한다. strict-origin-when-cross-origin으로 설정.
Permissions-Policy (구 Feature-Policy). 사이트가 사용할 수 있는 브라우저 기능을 제어한다: 카메라, 마이크, 위치정보. 사이트가 카메라를 안 쓰면 명시적으로 비활성화해서 XSS로도 활성화 못 하게. 공격 표면을 공짜로 줄인다.
Tier 3: 있으면 좋고, 상황에 따라
Cross-Origin-Opener-Policy (COOP), Cross-Origin-Resource-Policy (CORP), Cross-Origin-Embedder-Policy (COEP). 이해 없이 복사하고 임베드가 깨지면 탓하는 3인조.
이 헤더들은 브라우징 컨텍스트를 격리하고 교차 출처 격리 기능을 활성화한다. Spectre 계열 타이밍 공격 보호가 필요한 고보안 웹 앱에는 중요하다. 마케팅 사이트나 블로그에는 과하다. 스캐너가 없는 걸 보고 빨간색으로 표시하면 의미 있는 CSP도 배포 안 한 채 이것부터 추가하려고 달려든다. 순서가 거꾸로다.
Tier 4: 레거시와 화석
X-XSS-Protection. 전형적인 좀비 헤더. 현대 브라우저는 내장 XSS 필터를 전부 제거했다 — Chrome이 2019년에 XSS Auditor를 없앴다. 이 헤더를 설정해도 현재 브라우저에서 아무것도 안 한다. 일부 스캐너가 아직 부재를 지적하는데, 좀 짜증난다. 권장 값이 이제 0(끔)이다.
X-Permitted-Cross-Domain-Policies. Adobe Flash가 교차 도메인 데이터를 처리하는 방식을 제어한다. Flash는 죽었다. 이 문장이 박물관 투어처럼 들리면 맞다.
Cache-Control (보안 맥락). 엄밀히 보안 헤더는 아니지만, 민감한 페이지 — 계정 상세, 빌링, 일회성 토큰 — 는 no-store를 설정해야 한다. 팀에서 아무도 설명 못 하는 모호한 헤더 반 다스보다 가끔 더 가치 있다.
현실 세계의 격차
프로덕션 설정을 충분히 보면 패턴이 나타난다. 팀은 쉬운 헤더를 설정한다. 설정처럼 느껴지니까. 어려운 헤더는 건너뛴다. 엔지니어링처럼 느껴지니까.
nosniff는 쉽다. Referrer-Policy는 쉽다. CSP는 어렵다. 템플릿 습관, 인라인 스크립트, 분석 도구 확산, 배포 규율을 드러내니까. HSTS preload는 서브도메인에 대한 확신이 필요하다. 그래서 평균적 사이트는 작고 깔끔한 승리를 모으고, 실제로 아키텍처적 정직함을 강제하는 헤더 하나는 미룬다.
하나만 기억한다면: HSTS와 CSP가 완벽하고 X-Permitted-Cross-Domain-Policies가 없는 사이트가, 레거시 헤더는 전부 있지만 CSP가 고장 난 사이트보다 훨씬 나은 상태다. 깔끔한 스캐너 점수는 좋다. 공격 표면을 바꾸는 헤더가 더 좋다.