중요한 HTTP 보안 헤더: CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy 설명
HTTP 응답 헤더가 보안에 중요한 이유
모든 HTTP 응답에는 헤더가 포함되어 있습니다——본문 이전에 전송되는 메타데이터 줄로, 브라우저가 콘텐츠를 처리하는 방식을 제어합니다. 대부분의 헤더는 일반적(Content-Type, Cache-Control)이지만, 특정 보안 헤더 세트는 브라우저에게 전체 공격 카테고리를 차단하는 제한을 적용하도록 지시합니다. 이것들은 이론적인 강화 조치가 아닙니다; OWASP Top 10과 CVE 데이터베이스에 반복적으로 등장하는 공격 클래스에 대한 실용적인 방어책입니다. 설정하는 데 성능 비용이 거의 없습니다——각 응답에 약 200-400바이트만 추가됩니다——그리고 securityheaders.com 같은 도구는 공개 URL에 등급을 매겨 어떤 헤더가 누락되었는지 즉시 확인할 수 있게 합니다.
핵심은 보안 헤더가 서버가 아닌 브라우저에 의해 적용된다는 것입니다. 서버는 정책을 발표하고; 브라우저가 이를 시행합니다. 이는 고트래픽 사이트에서의 헤더 잘못된 설정이 모든 방문자의 브라우저에 동시에 영향을 미친다는 것을 의미합니다. 반대로, 올바른 헤더를 추가하면 단일 배포로 수년간 축적된 위험을 수정할 수 있습니다. 여기서 다루는 다섯 가지 헤더——CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy——각각은 공격자가 악용할 수 있는 서로 다른 브라우저 동작을 대상으로 합니다.
Content-Security-Policy: XSS 방화벽
크로스 사이트 스크립팅(XSS)은 OWASP Top 10에서 가장 일반적인 웹 취약점입니다. HTML에 <script> 태그를 삽입할 수 있는 공격자는 세션 쿠키를 훔치거나, 사용자를 리디렉션하거나, 양식 데이터를 유출할 수 있습니다. Content-Security-Policy(CSP)는 스크립트, 스타일, 이미지, 폰트 및 기타 리소스 유형에 대해 어떤 소스를 신뢰할 수 있는지 브라우저에 알려주는 화이트리스트 기반 헤더입니다. 지시어 Content-Security-Policy: default-src 'self'는 동일한 출처의 리소스만 허용하여 모든 외부 스크립트와 인라인 <script> 블록을 차단합니다. script-src 지시어는 JavaScript에만 기본값을 재정의합니다.
가장 큰 CSP 함정은 unsafe-inline 키워드입니다. script-src 'unsafe-inline'을 추가하면 인라인 스크립트가 다시 활성화되어 CSP의 XSS 보호가 완전히 무력화되지만, CSP 활성화 후 사이트가 깨졌을 때 가장 많이 찾는 해결책입니다. 올바른 대안은 nonce(논스)입니다: 각 페이지 로드마다 암호학적으로 무작위 값을 생성하여 헤더에 script-src 'nonce-r4nd0m'으로, 각 합법적인 <script> 태그에 nonce="r4nd0m"으로 포함합니다. 일치하는 nonce가 없는 주입된 스크립트는 인라인으로 나타나더라도 차단됩니다. 아직 엄격한 정책을 준비하지 못한 팀을 위해, Content-Security-Policy-Report-Only는 아무것도 차단하지 않으면서 수집기 엔드포인트에 위반 보고서를 전달합니다——정책을 적용하기 전에 영향을 측정하는 안전한 방법입니다.
HTTP Strict Transport Security: HTTPS 다운그레이드 공격 방지
SSL 스트리핑은 2009년 Black Hat에서 공개적으로 시연된 다운그레이드 공격입니다. 경로상의 활성 공격자가 사용자의 초기 HTTP 요청(HTTPS로의 리디렉션 이전)을 가로채서 암호화된 연결을 사용자에게 다시 제공하면서 일반 HTTP로 출처에 프록시합니다. 사용자는 일부 브라우저에서 자물쇠를 보지만, 공격자와 서버 사이의 연결은 암호화되지 않은 것입니다——자격 증명과 세션 쿠키가 평문으로 보입니다. HTTP Strict Transport Security(HSTS)는 max-age 지시어로 초 단위로 정의된 기간 동안 브라우저가 도메인에 대한 HTTP 연결을 완전히 거부하도록 지시함으로써 이 취약점을 해결합니다.
일반적인 강력한 HSTS 헤더는 Strict-Transport-Security: max-age=31536000; includeSubDomains; preload입니다. 31536000초의 max-age는 1년——HSTS 사전 로드 목록에 포함되기 위한 최소 요구사항입니다. includeSubDomains는 정책을 모든 서브도메인에 확장합니다. preload는 hstspreload.org에서 브라우저에 내장된 사전 로드 목록에 포함되려는 의도를 나타냅니다; 목록에 올라가면 사이트를 방문하여 헤더를 받기 전에도 브라우저가 HTTP 연결을 거부합니다. 중요한 제약: HSTS는 유효한 HTTPS 응답을 통해 전달되어야 합니다. HTTP를 통해 전송된 헤더는 무시됩니다. includeSubDomains를 배포하기 전에 테스트하세요——어떤 서브도메인에 유효한 TLS 인증서가 없으면, 사용자는 전체 max-age 기간 동안 해당 서브도메인에 접근할 수 없게 됩니다.
X-Content-Type-Options와 X-Frame-Options: 단일 값으로 얻는 두 가지 보안 향상
MIME 스니핑은 일부 브라우저가 응답의 처음 몇 바이트를 검사하여 콘텐츠 유형을 추측하고 Content-Type 헤더를 무시하는 레거시 동작입니다. Internet Explorer는 가장 공격적인 MIME 스니퍼였습니다; 처음 몇 바이트가 마크업처럼 보이면 .jpg를 HTML로 실행했습니다. 콘텐츠를 사이트에 업로드할 수 있는 공격자(이미지, 로그 파일)는 브라우저가 스니핑할 때 스크립트로 실행되는 페이로드를 만들 수 있습니다. X-Content-Type-Options: nosniff는 모든 최신 브라우저에 선언된 Content-Type을 신뢰하고 스니핑을 완전히 건너뛰도록 지시합니다. nosniff라는 하나의 유효한 값만 있습니다——모든 응답에 설정하지 않을 이유가 없습니다.
클릭재킹은 공격자가 제어하는 페이지의 <iframe> 안에 사이트를 삽입한 다음 투명한 버튼을 오버레이하여 사용자가 자신도 모르게 사이트의 UI 요소를 클릭하게 만듭니다. X-Frame-Options: DENY는 누구도 페이지를 프레임할 수 없도록 합니다; X-Frame-Options: SAMEORIGIN은 동일한 출처의 페이지만 프레이밍을 허용합니다. ALLOW-FROM uri 변형은 더 이상 사용되지 않으며 현재 브라우저에서 지원되지 않습니다. 현대적 동등물은 CSP frame-ancestors 지시어입니다(frame-ancestors 'none'은 DENY에 매핑되고, frame-ancestors 'self'는 SAMEORIGIN에 매핑됨), 여러 허용된 출처도 추가로 지원합니다. 모든 브라우저가 모든 컨텍스트에서 frame-ancestors를 확인하지는 않으므로, X-Frame-Options와 CSP frame-ancestors 지시어 모두 설정하면 심층 방어가 제공됩니다.
Referrer-Policy: Referer 헤더를 통한 정보 유출 제어
HTTP Referer 헤더(RFC에서 역사적 오타로 인해 'r'이 하나만 있음)는 사용자가 탐색해온 페이지의 전체 URL을 해당 페이지가 로드하는 모든 외부 리소스——이미지, 스크립트, 스타일시트 및 링크 클릭 대상——에 전송합니다. 로그인한 사용자가 /account/reset?token=eyJ...를 방문하고 해당 페이지가 타사 분석 스크립트를 로드하면, 스크립트의 요청은 리셋 토큰을 포함한 전체 URL을 Referer 헤더에 담아 전송합니다. referrer를 통한 토큰 유출은 잘 문서화된 취약점 클래스입니다; GitHub는 2020년에 referrer 기반 토큰 노출을 패치했습니다.
Referrer-Policy 헤더는 이전의 Meta referrer 메커니즘과 Referer 헤더 억제 해결책을 대체합니다. 정책 strict-origin-when-cross-origin은 권장되는 기본값입니다: 동일 출처 요청에는 전체 URL을 전송(내부 분석에 유용)하지만, 교차 출처 요청에는 순수 출처(https://example.com)만 전송하고, HTTPS에서 HTTP로 탐색할 때는 아무것도 전송하지 않습니다. no-referrer는 아무것도 전송하지 않아 개인 정보 보호를 극대화하지만 referrer에 의존하는 분석을 깨뜨립니다. unsafe-url은 경로와 쿼리 문자열을 포함한 전체 URL을 항상 전송합니다——2020년 이전 브라우저의 기본값이자 대부분의 역사적 referrer 유출의 원인입니다. Chrome, Firefox, Safari 모두 2020-2021년에 정책 헤더가 없을 때의 기본값으로 strict-origin-when-cross-origin을 채택했지만, 명시적으로 설정하면 모든 브라우저 버전에서 일관된 동작이 보장됩니다.
모든 사이트를 위한 실용적인 보안 헤더 기준선
정적 사이트나 간단한 웹 애플리케이션의 경우, 4개의 헤더가 최소한의 설정 복잡성으로 가장 일반적인 공격 벡터를 커버합니다: X-Content-Type-Options: nosniff(항상, 한 줄); X-Frame-Options: SAMEORIGIN(사이트가 합법적으로 삽입될 필요가 없는 한); Referrer-Policy: strict-origin-when-cross-origin; 그리고 Strict-Transport-Security: max-age=31536000; includeSubDomains(모든 서브도메인이 HTTPS임을 확인한 후). CSP는 더 많은 계획이 필요합니다——Content-Security-Policy-Report-Only와 보고 엔드포인트로 시작하여 며칠 동안 위반을 수집한 다음, 실제로 사용하는 것만 허용하는 정책을 작성하세요.
헤더 설정 위치는 스택에 따라 다릅니다: Nginx는 서버 블록에서 add_header 지시어를 사용합니다; Apache는 httpd.conf 또는 .htaccess에서 Header always set을 사용합니다; Vercel, Netlify, Cloudflare Pages 각각은 헤더 설정 파일이나 대시보드 설정이 있습니다. 대부분의 CDN은 엣지에서 헤더 주입을 허용하는데, 캐시된 응답에도 CDN이 헤더를 전달하기 때문에 애플리케이션 수준 헤더보다 선호됩니다. TeaFun HTTP Headers Checker 도구는 공개 URL을 가져와서 어떤 보안 헤더가 있는지, 그 값들, 각 헤더가 무엇을 하는지에 대한 빠른 참조 설명을 표시합니다——헤더 변경 사항을 배포하기 전후에 사이트를 감사하는 데 사용하세요.