보안 헤더 해설
보안 헤더는 HTTP 응답의 한 줄로, 방문자의 브라우저가 당신의 페이지로 무엇을 할 수 있는지를 바꿉니다: 이름 붙인 출처에서만 스크립트를 실행하기, 다시는 평문 HTTP로 말하지 않기, 프레임에 끼워지기를 거부하기, 콘텐츠 종류를 추측하지 않기. 어느 것도 코드도, 라이브러리도, 재시작도 필요 없습니다 — 그런데도 대부분의 웹사이트는 거의 또는 전혀 보내지 않고, 그래서 스캐너가 검사하면 대부분의 사이트가 F를 받습니다. 이 글은 헤더별로 각 헤더가 실제로 무엇을 막는지 차례로 설명하고, F의 유행을 해설하며, 작업 시간당 가장 많은 보호를 되찾는 수정 순서를 제시합니다. 자기 사이트의 판정을 보려면 먼저 보안 헤더 검사를 실행하고, 결과를 열어 둔 채 읽어 보세요.
헤더는 브라우저를 위한 지시이지, 서버의 갑옷이 아니다
헤더가 아닌 것부터 시작하세요. 보안 헤더는 애플리케이션에 패치를 하지 않고, 트래픽을 거르지 않으며, 누구도 인증하지 않습니다. 그것은 서버가 응답에 덧붙이는 선언이고, 브라우저 — 방문자의 브라우저, 방문자의 기계 위에 있는 — 가 그 순간부터 그것을 강제합니다. 이것은 많은 공격이 공격자를 대신해 브라우저에 의해 실행된다는 것을 알아차리기 전까지는 약하게 들립니다: 누군가 댓글 필드에 주입한 스크립트를 실행하기, 적대적인 사이트 위의 보이지 않는 프레임 안에 로그인 페이지를 불러오기, 내용이 스크립트처럼 보인다는 이유로 업로드된 텍스트 파일을 HTML로 취급하기. 헤더는 서버가 브라우저에게 미리 말하는 방법입니다 — 이 사이트에서는 그런 움직임이 결코 정당하지 않다고.
헤더는 또한 심층 방어이며, 그렇게 읽어야 합니다. 이 분야의 표준 참고 자료는 각 헤더의 구문과 의미를 규정하는 MDN HTTP 문서와, OWASP 웹 보안 테스팅 가이드의 테스팅 지침이 가리키는 권장 값을 가진 OWASP Secure Headers Project입니다. 어느 자료도 헤더가 사이트를 안전하게 만든다고 약속하지 않습니다. 그들이 약속하는 것은 더 좁고 참된 것입니다: 헤더를 설정하면, 애플리케이션 버그가 공격자에게 발판을 주어도 브라우저가 실행하는 공격의 전체 부류가 작동을 멈춘다는 것.
각 헤더가 실제로 무엇을 막는가
Content-Security-Policy는 주입된 스크립트의 실행을 막습니다. XSS와 그 사촌들 — 스크립트를 실은 저장형 댓글, 손상된 제3자 태그를 통해 주입된 스키밍 스크립트 — 은 모두 사이트가 의도한 적 없는 코드를 브라우저가 실행해야 합니다. CSP는 허용 목록입니다: script-src는 스크립트가 올 수 있는 오리진을 이름 붙이고, 인라인 스크립트와 공격자의 서버에서 불러오는 모든 것을 포함한 나머지는 거부됩니다. 그 약한 형태는 유명합니다: 'unsafe-inline'을 담은 정책은 주입된 것을 포함해 모든 인라인 스크립트를 실행하게 두며, 그래서 검사기는 그런 정책을 좋음이 아닌 약함으로 취급합니다. CSP는 배포하기 가장 무거운 헤더이자 가장 많이 막는 헤더입니다.
Strict-Transport-Security는 다운그레이드를 막습니다. 방문자가 처음으로 스킴 없이 도메인을 입력하거나 오래된 http:// 링크를 따라가면, 그 요청은 평문으로 네트워크를 건너며, 도중에 벗겨지거나, 리디렉션되거나, 읽힐 수 있습니다 — 세션 쿠키까지 포함해서. HSTS는 브라우저에게 말합니다: 다음 max-age 초 동안, 이 호스트의 모든 http:// URL을 어떤 것도 기계를 떠나기 전에 https://로 다시 쓰고, 인증서 오류의 클릭 통과를 거부하라고. 정직한 한계 하나: HSTS는 브라우저가 그 헤더를 이미 한 번 본 방문자만을 보호합니다(브라우저에 내장된 사전 로드 목록은 바로 그 첫 방문의 틈을 메우기 위해 존재합니다). 그리고 HSTS 자체가 HTTPS로 도착했을 때만 존중됩니다.
프레임 보호는 클릭재킹을 막습니다. 당신의 페이지가 프레임에 끼워질 수 있으면, 적대적인 사이트는 당신의 진짜 인터페이스를 자기 버튼들의 투명한 층 아래에 깔고 클릭을 거둬들일 수 있습니다 — 피해자는 무해해 보이는 페이지를 보고 송금을 승인합니다. CSP 지시문 frame-ancestors(그리고 그 선배인 X-Frame-Options: DENY 또는 SAMEORIGIN)는 누가 당신을 프레임에 넣을 수 있는지를 이름 붙입니다; 나머지는 모두 빈 프레임을 받습니다. X-Content-Type-Options: nosniff는 더 조용한 속임수를 막습니다: 브라우저는 역사적으로 선언된 Content-Type을 다시 추측하고, 내용이 그럴듯해 보이면 파일을 스크립트나 HTML로 실행해 무고한 업로드를 저장형 XSS로 바꿔 왔습니다. nosniff는 추측을 끝냅니다 — 선언된 종류가 그 종류입니다.
Referrer-Policy는 URL 유출을 막습니다. 방문자가 당신의 페이지에서 다른 사이트로 클릭하면, 브라우저는 역사적으로 당신의 페이지의 전체 URL을 Referer로 보냈습니다 — 그 안에 앉아 있는 비밀번호 재설정 토큰, 검색어, 레코드 ID까지 포함해서. strict-origin-when-cross-origin 또는 그보다 엄격한 정책은 사이트를 건널 때 오리진만 보내고, 다운그레이드되기 쉬운 http 대상에는 아무것도 보내지 않습니다. Permissions-Policy는 기능 남용을 막습니다: 이 사이트가 애초에 어떤 강력한 브라우저 능력 — 카메라, 마이크, 위치 정보, 결제 핸들러 — 을 쓸 수 있는지를 선언하므로, 주입된 프레임이나 스크립트가 당신의 이름으로 그것들을 조용히 요청할 수 없게 됩니다. 그리고 보너스 헤더인 Cross-Origin-Opener-Policy는 당신의 페이지와 다른 오리진의 팝업 사이의 window.opener 연결을 끊어, 당신의 브라우징 맥락을 그것을 탐색하려는 페이지들로부터 격리합니다.
왜 이렇게 많은 사이트가 F인가
누구의 기본값도 이 헤더들을 보내지 않기 때문입니다. 순정 웹 서버, 순정 프레임워크 비계, 순정 정적 호스트: 각각 여섯 중 최대 하나나 둘만을 담은 응답을 보내고, 가장 가치 있는 둘 — CSP와 Permissions-Policy — 은 구체적인 사이트에 대한 결정을 필요로 하므로, 어떤 기본값도 안전하게 함께 보낼 수 없습니다. 그러면 책임은 셋으로 갈라집니다: 프레임워크 팀은 서버가 헤더를 더한다고 가정하고, 서버 팀은 프레임워크가 더한다고 가정하며, 앞에 있는 CDN은 어차피 마음대로 다시 씁니다. 헤더는 그 틈으로 조용히 떨어집니다. 그것들이 없어도 눈에 띄게 깨지는 것이 아무것도 없기 때문입니다. F인 사이트는 보통 A인 사이트와 똑같이 보이고 똑같이 동작합니다; 차이는 공격이 브라우저의 협력을 필요로 하는 바로 그날에만 드러납니다.
공개된 숫자는 그 이야기와 일치합니다. 2026-11-04의 주간 도구 스캔 총괄에서 보도된 한 스캐너의 공개 표본은 스캔된 사이트의 65%가 F이고 51%가 Content-Security-Policy를 전혀 보내지 않는다는 것을 발견했습니다. 이것들을 본래의 모습으로 대하세요 — 한 스캐너의 표본이며, 제3자가 공개한 것이고, 웹 전체의 측정도, 우리 자신의 데이터도 아닙니다 — 하지만 방향은 모든 운영자의 경험과 일치합니다: 헤더는 부재가 통계적 정상인 드문 보안 통제입니다. 그리고 그것은 이 통계에 숨은 좋은 소식이기도 합니다: 기준선은 바닥에 있고, 그것을 넘는 것은 엔지니어링이 아니라 설정입니다.
수정 순서: 시간당 영향으로
보안 헤더 검사가 분명히 보여 주는 순서대로 작업하세요. 먼저, 한 줄짜리들: X-Content-Type-Options: nosniff, 프레임 보호, 그리고 합리적인 Referrer-Policy — 100점 중 35점, 몇 분의 작업, 그리고 보통의 사이트에서는 깨짐 위험이 사실상 0입니다. 둘째, HSTS — 20점 더 — 단, 모든 페이지, 하위 도메인, 레거시 리디렉션이 실제로 HTTPS로 동작하는 것을 확인한 뒤에만. 긴 max-age는 되돌릴 수 없기 때문입니다. 셋째, Permissions-Policy: 사이트가 실제로 쓰는 기능을 나열하고 나머지를 끄세요; 조심스러운 한 줄로 10점입니다.
마지막으로, 그리고 천천히, CSP. 그 35점은 가장 큰 단일 덩어리이며 자기 사이트를 쓰러뜨릴 수 있는 유일한 수정입니다. 먼저 보고 엔드포인트와 함께 Content-Security-Policy-Report-Only로 배포하고, 한 주 분량의 위반 보고를 읽고, 그것이 지목하는 인라인 스크립트와 이벤트 핸들러를 다시 쓰거나 — MDN 지침이 권하듯 nonce를 채택하거나 — 하고, 보고가 조용해졌을 때만 강제 적용으로 전환하세요. 다른 오리진의 팝업 흐름에 의존하지 않는 한 Cross-Origin-Opener-Policy: same-origin을 더하세요; 우리 표에서는 채점되지 않지만 값싼 격리입니다. 그런 다음 검사를 다시 실행해, 원본에서 더한 헤더가 그 앞의 CDN과 프록시를 실제로 살아남았는지 확인하세요.
헤더가 할 수 없는 것
헤더는 구멍을 고칠 수 없고, 공격자가 그 구멍을 통해 할 수 있는 것을 줄일 뿐입니다. CSP는 입력을 이스케이프하지 못한 댓글 필드를 고치지 않습니다; 주입된 페이로드가 실행을 거부하게 만들 뿐입니다. HSTS는 깨진 인증서 체인을 고치지 않습니다 — 그것은 SSL 채점기의 영역입니다 — 그리고 어떤 헤더도 패치, 백업, 접근 통제를 대신하지 않습니다. 완벽한 헤더 등급은 다른 층들이 여전히 각자의 검사를 필요로 하는 건물의 잘 잠긴 문으로 읽으세요. 그것이 바로 웹사이트 상태 검사의 용도입니다.
헤더는 또한 설정하고 잊을 수도 없습니다. 작년의 사이트를 위해 쓰인 CSP는 올해의 결제 위젯을 조용히 막습니다 — 아니면 더 나쁘게, 서둘러 *로 느슨해져 아무 의미도 없어집니다. 헤더 집합을 나머지 설정과 똑같이 대하세요: 버전을 매기고, 사이트가 바뀔 때 검토하고, 플랫폼이 바뀔 때마다 다시 검사하세요. 검사는 10초면 됩니다; 페이지 앞의 스택이 바뀔 때마다 실행하면, F의 유행은 다른 누군가의 통계로 남습니다.