preload가 일반 HSTS 헤더에 더해 주는 것
Strict-Transport-Security 헤더는 브라우저에 “앞으로 max-age초 동안 이 호스트와는 HTTPS로만 통신하라”고 알려 줍니다. http://를 입력하거나 예전 링크를 눌러도, 브라우저가 요청이 기기를 떠나기 전에 스스로 업그레이드합니다. 인증서 오류도 ‘계속 진행’할 수 있는 경고가 아니라 넘어갈 수 없는 실패가 됩니다.
빈틈은 첫 방문입니다. 내 헤더를 한 번도 본 적 없는 브라우저는 사용자가 입력하거나 누른 대로 첫 요청을 평문 HTTP로 보내고, 네트워크상의 공격자는 그 한 번의 요청을 가로채 사용자를 HTTP에 묶어 둘 수 있습니다. 방문자가 max-age가 끝나기 전에 다시 오지 않으면 같은 빈틈이 다시 열립니다.
preload 목록이 그 빈틈을 막습니다. Chromium 소스 코드에 들어가는 도메인 목록입니다. 크롬과 다른 Chromium 기반 브라우저는 이 목록을 그대로 쓰고, 파이어폭스와 사파리는 이를 바탕으로 자체 목록을 관리합니다. 브라우저는 설치된 순간부터 목록에 있는 도메인을 HTTPS 전용으로 다루므로 첫 요청이 필요 없습니다.
이점은 그게 전부입니다. 첫 요청입니다. 나머지는 헤더가 이미 해 줍니다.
애초에 해야 하나?
제 의견은 이렇습니다. HTTPS 사이트라면 어디든 헤더는 보내세요. 가능하면 긴 max-age와 includeSubDomains까지 넣습니다. preload는 아래 세 가지가 모두 맞을 때만 하세요.
- 모든 서브도메인을 포함해 도메인 전체가 지금 HTTPS 전용이고, 앞으로도 그렇게 유지됩니다.
- 내부용을 포함해 모든 서브도메인을 관리하거나, 적어도 전부 파악할 수 있습니다.
- 첫 요청부터 보호할 가치가 있는 공격 대상입니다. 은행, 메일, 관리 콘솔처럼 공격자가 가로챌 이유가 있는 로그인이 있는 곳입니다.
하나라도 흔들리면 preload의 이득은 작고, 손해는 빨리 되돌릴 수 없다는 것입니다.
요건, 정확하게
hstspreload.org가 제시하는 요건이고, 제출 폼이 자동으로 확인합니다.
- 유효한 인증서를 제공합니다.
- 80번 포트를 연다면 HTTP를 같은 호스트의 HTTPS로 리다이렉트합니다.
http://example.com은https://www.example.com으로 바로 가지 말고 먼저https://example.com으로 가야 합니다. 그래야 브라우저가 apex에서 HSTS 헤더를 봅니다. - 모든 서브도메인을 HTTPS로 제공합니다. 특히
www에 DNS 레코드가 있다면www가 HTTPS로 동작해야 합니다. - HTTPS 기본 도메인에서 HSTS 헤더를 보냅니다. 조건은 다음과 같습니다.
max-age가31536000(1년) 이상includeSubDomainspreloadhttps://example.com이 다른 곳으로 리다이렉트한다면, 그 리다이렉트 응답 자체에 헤더가 있어야 합니다.
제출할 수 있는 것은 등록 가능한 도메인뿐이고(shop.example.com이 아니라 example.com), 그 항목은 아래의 모든 이름에 적용됩니다.
지금 상태 확인하기
# 1. HTTP는 같은 호스트의 HTTPS로 리다이렉트돼야 함
curl -s -o /dev/null -D - http://example.com/ | grep -iE '^(HTTP/|location:)'
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/
# 2. HTTPS apex는 (리다이렉트하더라도) 헤더를 보내야 함
curl -s -o /dev/null -D - https://example.com/ | grep -iE '^(HTTP/|location:|strict-transport-security:)'
# HTTP/2 301
# location: https://www.example.com/
# strict-transport-security: max-age=63072000; includeSubDomains; preload
# 3. www는 HTTPS로 동작해야 함
curl -sI https://www.example.com/ | grep -i '^HTTP/'
HTTP 점검은 리다이렉트 체인과 최종 페이지가 보내는 HSTS 헤더를 보여 줍니다. SSL 점검도 HSTS 헤더를 읽어 등급에 반영합니다(자세한 내용은 이 페이지 끝에 있습니다). 둘 다 도메인이 실제로 preload 목록에 있는지는 알려 주지 않습니다. 그건 hstspreload.org의 상태 조회로 확인하세요.
진짜 위험: 모든 서브도메인, 영원히
preload된 항목의 includeSubDomains는 지금과 앞으로의 모든 하위 이름이, 목록을 담은 모든 브라우저에서 HTTPS 전용이 된다는 뜻입니다. 잊고 있던 이름도 포함됩니다.
- 사내 DNS에서 HTTP로 운영되는 위키를 가리키는
intranet.example.com printer.office.example.com에 있는 프린터, NAS, 공유기, 카메라 관리 페이지- 개발자 PC에서 자체 서명 인증서로 도는
dev.example.com - 어딘가에서 HTTP로 리다이렉트하는, 외부 업체가 운영하는
status.example.com이나mail.example.com - 이 글을 읽지 않은 누군가가 내년에 새로 만들 무언가
그런 이름에서 브라우저는 평문 HTTP를 거부합니다. 인증서가 자체 서명이거나 만료됐거나 이름이 다르면, HSTS가 금지하므로 ‘계속 진행’ 버튼이 없는 오류 페이지가 뜹니다. 해결책은 그 호스트에 유효한 인증서를 주는 것뿐이고, 그때까지는 브라우저로 접속할 수 없습니다.
제출 전에 목록을 만드세요.
- 공개 DNS 존을 내보내서 이름마다 HTTPS로 확인합니다.
- 공인 인증서를 받은 적 있는 이름은 인증서 투명성(CT) 로그에 남습니다. 서브도메인 조회가 CT 로그에서 그 목록을 보여 줍니다.
- 그다음 사내 DNS를 확인합니다. CT 로그에는 공인 인증서를 받은 이름만 보이는데, preload가 망가뜨리는 HTTP 전용 내부 호스트가 바로 그런 인증서를 받은 적 없는 이름들입니다.
HTTPS 전용으로 만들 수 없는 서브도메인이 있다면 이 도메인은 preload하지 마세요. includeSubDomains 없이 헤더만 쓰거나, 그 서비스를 다른 도메인 아래로 옮기세요.
max-age는 한 번에 올리지 말고 단계적으로
실수가 빨리 만료되도록 짧게 시작해서, 아무것도 깨지지 않으면 늘립니다. hstspreload.org가 권하는 단계입니다.
max-age=300; includeSubDomains # 5분
max-age=604800; includeSubDomains # 1주
max-age=2592000; includeSubDomains # 1개월
max-age=63072000; includeSubDomains; preload # 2년, 그다음 제출
실제 사용자들이 모든 서브도메인을 한 번씩 거칠 만큼 각 단계에 머물고, 내부 서비스에 “접속이 안 된다”는 문의가 오는지 지켜보세요. 최종값으로는 흔히 2년을 권하고, preload의 최소 요건은 1년(31536000)입니다.
어느 단계에서 문제가 생기면 HTTPS로 max-age=0을 보내 브라우저가 그 호스트의 동적 HSTS 항목을 잊게 할 수 있습니다. 헤더로 배운 것에만 통하고, preload 항목에는 통하지 않습니다.
헤더 설정하기
nginx:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
always는 오류 응답에도 헤더를 붙입니다. nginx의 상속 규칙을 조심하세요. location 블록에 자기 add_header가 하나라도 있으면 server 블록의 add_header를 상속하지 않아서, 그 URL들에서는 HSTS 헤더가 조용히 사라집니다. nginx -T | grep -n add_header로 확인하고, 경로 몇 개를 curl로 시험해 보세요.
Apache(mod_headers):
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
HTTPS 가상 호스트에 넣으세요. 브라우저는 평문 HTTP로 받은 헤더를 무시하므로, 80번 포트에서 보내 봐야 소용없습니다.
Cloudflare에서는 SSL/TLS → Edge Certificates에 HSTS 설정이 있고, max-age, includeSubDomains, preload를 각각 켭니다. 존의 모든 호스트명에 적용되므로 주의할 점은 똑같습니다.
제출, 그리고 그다음
hstspreload.org에 기본 도메인을 제출합니다. 폼이 실제 사이트를 대상으로 모든 요건을 다시 확인하고, 무엇이 안 맞는지 정확히 알려 줍니다. 승인되면 도메인이 Chromium의 목록에 추가되고, 그 목록을 담은 브라우저 릴리스가 나와야 사용자에게 적용됩니다. 보통 몇 주에서 몇 달이 걸리고, 다른 브라우저는 각자 일정에 따라 반영합니다.
등록 뒤에도 preload가 든 헤더를 계속 보내세요. hstspreload.org는 요건을 계속 충족해야 한다고 안내합니다.
어떤 최상위 도메인은 통째로 preload돼 있습니다. 예를 들어 구글의 .app과 .dev가 그렇습니다. 그 아래의 이름은 제출하든 안 하든 이미 HTTPS 전용입니다.
제거는 느립니다. 영구적이라고 생각하세요
목록에서 빠져야 한다면 헤더에서 preload를 지우고 hstspreload.org/removal에 도메인을 제출합니다. 그다음 제거도 등록 때처럼 브라우저 릴리스에 실려야 하므로 몇 달이 걸립니다. 예전 버전 브라우저를 쓰는 사용자는 업데이트할 때까지 예전 목록을 그대로 쓰고, 끝내 업데이트하지 않는 사람도 있습니다. 그동안 HTTP 전용 서브도메인은 그 사용자들에게 계속 접속 불가입니다.
이 체크리스트가 있는 진짜 이유가 이것입니다. preload는 몇 분이면 걸지만, 되돌리는 일은 내 일정대로 되지 않습니다.
내 브라우저에서 확인하기
크롬의 chrome://net-internals/#hsts(엣지는 edge://net-internals/#hsts)에서 도메인을 조회할 수 있습니다. preload된 도메인은 static_upgrade_mode: FORCE_HTTPS로, 헤더로 배운 도메인은 dynamic_upgrade_mode 항목으로 나옵니다. 같은 페이지의 “Delete domain security policies”는 동적 항목만 지웁니다. preload 항목은 로컬에서 지울 수 없습니다.
DechoNet이 HSTS를 채점하는 방식
SSL 점검은 100점 중 20점을 HSTS에 배정하고, preload 목록이 아니라 HTTPS 사이트가 보내는 헤더로 채점합니다.
| 헤더 | 점수 |
|---|---|
max-age ≥ 31536000, includeSubDomains와 preload 포함 | 20 |
max-age ≥ 31536000, includeSubDomains 포함 | 15 |
max-age ≥ 31536000만 | 10 |
그보다 짧은 max-age | 5 |
| 헤더 없음 | 0 |
그래서 일부러 preload를 하지 않기로 했어도 preload 지시어가 없으면 5점이 깎이고, preload된 도메인이라도 헤더를 보내지 않으면 20점을 모두 잃습니다. HTTP 점검은 HSTS를 따로, 채점하는 보안 헤더 6개 중 하나로 다루고 max-age가 1년 미만이면 약함으로 표시합니다. 나머지 헤더는 HTTP 보안 헤더 가이드에서 다룹니다.
내 도메인에서 바로 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.