HSTS가 막는 것과 못 막는 것: max-age, includeSubDomains, preload
Strict-Transport-Security 헤더를 받은 브라우저가 무엇을 바꾸는지 RFC 6797로 정리합니다. 첫 방문 문제, includeSubDomains의 이점과 위험, 단계적 max-age, 지금 preload를 권장하지 않는 이유까지.
http://로 시작하는 링크를 눌렀는데 브라우저가 평문 요청을 보내지도 않고 주소를 https://로 바꿔 버리는 사이트가 있습니다. 그 사이트가 예전에 보낸 Strict-Transport-Security 응답 헤더를 브라우저가 기억하고 있기 때문입니다. 이 동작을 정의한 것이 RFC 6797의 HSTS(HTTP Strict Transport Security)입니다.
리다이렉트만으로는 부족한 이유
서버가 301 리다이렉트로 HTTPS로 보내 주더라도 첫 요청은 평문으로 나갑니다. hstspreload.org는 이 지점에서 생기는 공격을 설명합니다. 경로 위의 공격자는 리다이렉트를 가로채 다시 써서 브라우저를 계속 HTTP에 붙잡아 둘 수 있고, HTTP 요청의 쿠키를 보거나 고칠 수 있으며, 리다이렉트 응답에 쿠키를 끼워 넣을 수도 있습니다. HSTS 정책을 가진 브라우저는 그 평문 요청을 아예 보내지 않습니다.
브라우저가 하는 일
Strict-Transport-Security: max-age=31536000; includeSubDomains
max-age는 필수 지시어로, 헤더를 받은 시점부터 몇 초 동안 이 호스트를 ’알려진 HSTS 호스트(Known HSTS Host)’로 취급할지 정합니다. includeSubDomains는 선택 지시어이고, 정책을 모든 하위 도메인으로 넓힙니다. RFC 6797이 정의한 지시어는 이 둘뿐이며, 브라우저는 모르는 지시어를 무시하고 나머지를 처리해야 합니다.
브라우저는 이 헤더를 오류 없는 보안 연결로 받은 응답에서만 기록합니다. HTTP 응답에 든 헤더는 무시해야 하고(MUST), 서버도 HTTP 응답에 이 헤더를 넣으면 안 됩니다(MUST NOT). HTML의 <meta http-equiv>로 넣은 값도 따르지 않으며, IP 주소로 접속한 호스트는 기록하지 않습니다. 한 응답에 헤더가 여러 개 오면 첫 번째만 처리합니다.
기록이 살아 있는 동안 브라우저는 그 호스트의 http URI를 불러오기 전에, 리다이렉트를 따라갈 때도 포함해, 스킴을 https로 바꿉니다. 명시된 포트가 80이면 443으로 바꾸고, 다른 포트는 그대로 둡니다. TLS 연결에서 인증서 검증 실패를 비롯해 어떤 오류나 경고가 나와도 연결을 끊어야 합니다(MUST). RFC는 이때 사용자가 경고를 누르고 넘어갈 수 있게 하지 말라고 권하는데, 이 부분은 비규범 구현 권고입니다.
나중 응답에서 헤더가 빠져도 정책은 max-age가 끝날 때까지 남습니다. 정책을 지우려면 HTTPS로 max-age=0을 보내야 하고, 이때 함께 붙은 includeSubDomains는 무시됩니다.
첫 방문은 보호하지 못한다
브라우저가 헤더를 받으려면 먼저 한 번 접속해야 합니다. 아직 정책이 없는 호스트에 사용자가 http 주소를 입력하거나 http 링크를 따라가면 첫 요청은 평문으로 나가고, RFC 6797은 이를 부트스트랩 MITM 취약점이라고 부릅니다. 만료된 기록은 지워지므로 max-age가 지난 뒤의 첫 접속도 같은 처지입니다.
RFC가 완화책으로 든 것 중 하나가 브라우저에 미리 넣어 두는 사전 적재 목록(pre-loaded list)입니다. Chrome은 hstspreload.org를 통해 이 목록을 관리하고, Firefox, Safari, Edge 등 다른 주요 브라우저도 Chrome 목록을 바탕으로 한 목록을 갖고 있습니다.
includeSubDomains의 양면
RFC 6797은 이 지시어가 없으면 Secure 속성이 붙은 도메인 쿠키를 지킬 수 없다고 설명합니다. 공격자가 example.com 아래에 원래 없던 하위 도메인을 DNS에 등록해 자기 서버로 향하게 하고 브라우저가 그곳에 요청하게 만들었다고 해 봅시다. 그 하위 도메인에는 HSTS 정책이 없으니, 사용자가 인증서 경고를 누르고 넘어가면 쿠키가 공격자에게 넘어갈 수 있습니다.
반대로 HTTPS를 제대로 제공하지 않는 하위 도메인은 정책을 받은 브라우저에서 접속할 수 없게 됩니다. RFC가 드는 예는 CRL과 OCSP를 평문 HTTP로 제공하는 인증기관입니다. ca.example.com이 includeSubDomains를 보내면 그 브라우저는 crl-and-ocsp.ca.example.com에서 인증서 상태를 가져오지 못합니다. hstspreload.org도 외부에 공개하지 않은 사내 하위 도메인까지 포함해 모두 HTTPS로 동작하는지 먼저 확인하라고 합니다.
헤더를 어디서 보내는지도 따져야 합니다. example.com만 헤더를 보내고 사용자는 늘 www.example.com으로 들어온다면, example.com에 접속한 적 없는 브라우저는 정책을 받지 못합니다. RFC는 진입점이 되는 호스트마다 헤더를 직접 보내라고 권합니다(비규범).
max-age는 단계적으로 늘린다
hstspreload.org의 배포 권고는 모든 HTTPS 응답에 헤더를 넣고, includeSubDomains를 붙인 채 max-age를 300(5분), 604800(1주), 2592000(1개월) 순으로 올리는 방식입니다. 단계마다 깨진 페이지와 트래픽 같은 지표를 확인하고, 그 단계의 max-age를 다 기다린 뒤 다음으로 넘어갑니다. 헤더를 받은 브라우저는 max-age가 끝나거나 HTTPS로 max-age=0을 받기 전까지 정책을 유지하기 때문에, 짧은 값으로 시작해야 실수했을 때 영향받는 기간도 짧습니다. RFC 6797도 서버 설정 시스템의 기본 max-age를 0으로 두어, 운영자가 모르는 사이에 HSTS가 켜지는 일을 막으라고 권합니다(비규범).
preload는 지금 권장되지 않는다
hstspreload.org의 신청 요건은 유효한 인증서, 80번 포트를 열어 둔 경우 같은 호스트에서의 HTTP→HTTPS 리다이렉트, 모든 하위 도메인의 HTTPS, 그리고 기준 도메인에서 max-age가 31536000초(1년) 이상이고 includeSubDomains와 preload가 붙은 헤더입니다. preload는 RFC 6797이 정의한 지시어가 아니라, 이 목록에 올려 달라는 의사 표시입니다.
2026년 10월 13일에 확인한 hstspreload.org는 HSTS는 권장하지만 HSTS preload는 권장하지 않는다고 밝힙니다. Chrome과 Safari를 비롯한 많은 브라우저가 도메인의 HSTS 정책과 관계없이 HTTP 탐색을 HTTPS로 자동 업그레이드하므로, preload는 능동적 공격자 때문에 그 업그레이드가 실패할 때만 가치가 있다는 이유입니다. 목록에서 빠지는 것도 쉽지 않습니다. 제거된 내용이 Chrome 업데이트로 사용자에게 닿기까지 몇 달이 걸리고, 다른 브라우저에 대해서는 보장하지 않는다고 합니다. 그래서 HTTPS 설정 도구나 가이드에는 preload를 기본값으로 넣지 말라고 요청합니다.
범위
RFC 6797(2012)은 Standards Track 문서이고, 위의 MUST와 MUST NOT은 그 규범 요구입니다. 서버 배포 권고(11절)와 브라우저 구현 권고(12절)는 RFC가 스스로 비규범이라고 밝힌 부분입니다. 단계적 max-age, preload 신청 요건, preload를 권장하지 않는 입장은 Chrome 사전 적재 목록의 신청 사이트인 hstspreload.org의 정책이며 표준이 아니므로 바뀔 수 있습니다.
참고
- RFC 6797, HTTP Strict Transport Security (HSTS) (2012)
- HSTS Preload List Submission (2026년 10월 13일 확인)