HSTS
고친 사람 github-actions[bot]
HSTS 는 브라우저가 한 사이트에 암호화된 연결로만 들어가게 만듭니다. 사이트는 응답에 한 줄을 실어 이 요구를 알립니다. 브라우저는 그 요구를 정해진 기간 동안 기억합니다. 헤더를 받은 뒤부터는 중간에서 누군가 가로챌 암호화 없는 요청이 나가지 않습니다.
쉽고 빠른 이해
HSTS 는 브라우저에게 「이 사이트는 암호화된 연결로만 들어와라」라고 일러 두는 한 줄입니다.
은행 사이트가 이 한 줄을 보내 두었다고 합시다. 사용자가 http:// 로 주소를 쳐도 브라우저가
알아서 https:// 로 바꿔 접속합니다.
이게 없으면 주소를 칠 때마다 요청 하나가 암호화 없이 먼저 나갑니다. 사이트가 암호화된 주소로 넘겨 주는 것은 그 뒤입니다. 그 틈에 끼어든 공격자는 사용자를 암호화 없는 연결에 계속 붙잡아 둘 수 있습니다.
- 암호화된 연결로 받은 응답에서 이 한 줄을 보면 브라우저가 사이트 이름과 기간을 적어 둡니다
- 기간 안에는 그 사이트로 가는 주소를 보내기 전에 암호화된 주소로 바꿉니다
- 인증서가 이상하면 경고를 넘어가는 버튼 없이 연결을 끊습니다
대가는 되돌리기 어렵다는 점입니다. 사이트가 암호화를 그만두면 이 요구를 기억한 브라우저는 기간이 끝날 때까지 그 사이트에 못 들어갑니다. 그리고 헤더를 한 번도 받은 적 없는 첫 방문은 막지 못합니다.
상세
웹 서버와 브라우저가 주고받는 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 메시지에는 본문 앞에 헤더가 붙습니다. 헤더는 「이 응답을 어떻게 다뤄 달라」 같은 부가 정보를 한 줄씩 적은 것입니다. HSTS(HTTP Strict Transport Security, HTTP 엄격 전송 보안)는 이 헤더 한 줄로 브라우저의 접속 방법을 못 박는 표준입니다.
못 박는 내용은 하나입니다. 이 사이트에는 정해진 기간 동안 HTTPS(HyperText Transfer Protocol Secure)로만 접속하라는 것입니다. HTTPS 는 HTTP 메시지를 TLS(Transport Layer Security, 전송 계층 보안)로 암호화해 주고받는 방식입니다.
이 요구를 HSTS 정책이라고 부릅니다. 아래에서 줄여 정책이라고 쓰면 이것을 가리킵니다. 정책을
싣는 헤더의 이름은 Strict-Transport-Security 입니다.
HSTS 를 정한 문서는 RFC 6797 입니다. RFC(Request for Comments)는 인터넷 기술의 규칙을 적어 공개하는 문서 묶음입니다. 문서마다 번호가 붙습니다.
리다이렉션 전 평문 요청의 틈
이 소절은 HSTS 가 왜 필요한지를 공격 하나로 보입니다. HSTS 가 없는 사이트에 사용자가 들어가는 순간을 따라갑니다.
사용자는 주소창에 example.com 처럼 앞머리 없이 치거나 http:// 로 시작하는 링크를 누릅니다.
그러면 브라우저는 요청을 암호화 없이 보냅니다. 암호화하지 않은 채 오가는 데이터를
평문이라고 부릅니다.
서버는 보통 이 평문 요청에 「HTTPS 주소로 다시 오라」는 응답을 돌려줍니다. 이렇게 다른 주소로 다시 가게 하는 응답을 리다이렉션이라고 합니다. 브라우저는 그 응답을 받고 나서야 암호화된 연결로 갈아탑니다.
문제는 갈아타기 전에 나간 평문 요청입니다. 공용 와이파이처럼 통신 경로 중간에 누군가 설 수 있는 곳에서는 그 요청을 가로챌 수 있습니다. 통신하는 두 쪽 사이에 몰래 끼어들어 오가는 내용을 엿보거나 바꾸는 공격을 중간자 공격이라고 부릅니다.
끼어든 공격자는 리다이렉션을 브라우저에 넘기지 않습니다. 공격자 자신이 서버와 HTTPS 로 통신합니다. 브라우저에게는 받은 내용을 평문으로 풀어 전해 줍니다.
그래서 사용자는 계속 평문 연결에 머뭅니다. 입력한 비밀번호와 쿠키가 공격자에게 다 보입니다. 그림은 공격자가 두 연결 사이에 서서 한쪽만 암호화를 쓰는 모양입니다.
sequenceDiagram
participant 브라우저
participant 공격자
participant 서버
브라우저->>공격자: http 로 요청 (평문)
공격자->>서버: https 로 대신 요청
서버-->>공격자: 암호화된 응답
공격자-->>브라우저: 평문으로 풀어 전달
Note over 브라우저,공격자: 사용자는 평문 연결에 머문다
이 수법을 SSL 스트리핑이라고 부릅니다. SSL(Secure Sockets Layer)은 TLS 의 옛 이름입니다. 연결에서 암호화를 벗겨 낸다는 뜻으로 붙은 이름입니다.
HSTS 는 헤더를 받은 뒤부터 이 평문 요청을 없앱니다. 주소를 칠 때마다 리다이렉션 전에 나가던 요청입니다. 브라우저가 이 사이트는 HTTPS 전용이라는 것을 미리 알면 평문 요청을 아예 내보내지 않습니다. 공격자가 끼어들 요청이 사라집니다.
헤더의 생김새
이 소절은 헤더 한 줄을 뜯어 각 부분이 무엇을 정하는지 봅니다. 서버가 보내는 헤더는 이렇게 생겼습니다.
Strict-Transport-Security: max-age=31536000; includeSubDomains
콜론 뒤의 값은 세미콜론으로 나뉜 지시어 몇 개로 이루어집니다. 지시어는 브라우저가 이 정책을
어떻게 적용할지 정하는 설정 한 조각입니다. 위 줄에는 기간을 정하는 max-age 와 적용 범위를
넓히는 includeSubDomains 가 들어 있습니다.
max-age 의 값은 초 단위입니다. 31536000 초는 60초 × 60분 × 24시간 × 365일, 곧 1년입니다.
같은 이름의 지시어가 Cache-Control 헤더에도 있습니다. 가리키는 기한은 다릅니다. 캐시 쪽은 응답을 서버에 다시 묻지 않고 쓸 기간입니다. HSTS 쪽은 정책을 기억할 기간입니다.
호스트는 요청을 받는 서버를 가리키는 이름입니다. www.example.com 이 한 예입니다. 정책은
헤더를 보낸 그 호스트에 붙습니다.
서브도메인은 example.com 앞에 이름을 한 칸 더 붙인 api.example.com 같은 이름입니다. 한
회사가 여러 서비스를 서브도메인으로 나눠 띄우는 일이 흔합니다. 그래서 정책을 그 아래까지
넓힐지를 따로 정합니다. 지시어 셋을 모으면 이렇습니다.
| 지시어 | 정하는 것 |
|---|---|
max-age |
이 정책을 기억할 기간. 초 단위. 반드시 적는다 |
includeSubDomains |
헤더를 보낸 호스트 아래의 서브도메인에도 같은 정책을 적용한다 |
preload |
브라우저에 내장된 목록에 올려 달라는 신청 표시 |
표의 셋째 줄 preload 는 HSTS 표준에 없는 지시어입니다. 뒤의 「첫 방문의 빈틈과 프리로드
목록」 소절에 나오는 내장 목록에 등록을 신청할 때 쓰는 관례입니다.
브라우저가 헤더를 받은 뒤
이 소절은 헤더를 받은 브라우저가 무엇을 기억하고 그 뒤 요청을 어떻게 다루는지 봅니다. 마지막에 그 흐름을 상태 그림 하나로 모읍니다.
헤더를 받기 전의 호스트는 브라우저에게 모르는 호스트입니다. HTTPS 로 받은 응답에서 이 헤더를 찾으면 브라우저는 그 호스트 이름과 만료 시각을 적어 둡니다. 그 뒤로 이 호스트는 알려진 HSTS 호스트가 됩니다.
평문 HTTP 로 받은 응답에 이 헤더가 있으면 브라우저는 무시해야 합니다. 평문 응답은 공격자가 바꿔 넣을 수 있기 때문입니다. 그 헤더를 믿으면 공격자가 가짜 정책을 심거나 정책을 지울 수 있습니다.
숫자로만 된 IP(Internet Protocol) 주소로 접속한 호스트는 알려진 HSTS 호스트가 되지 않습니다. 정책은 도메인 이름에만 붙습니다.
알려진 HSTS 호스트로 가는 요청은 보내기 전에 주소부터 고칩니다. 브라우저는 http:// 를
https:// 로 바꿉니다. 그래서 평문 요청이 아예 나가지 않습니다.
포트는 한 서버 안에서 어느 프로그램이 요청을 받을지 가르는 번호입니다. 80 은 HTTP 의, 443 은 HTTPS 의 기본 포트입니다. 주소에 포트 번호 80 이 적혀 있으면 브라우저는 443 으로 바꿉니다.
인증서는 이 서버가 정말 그 도메인의 주인임을 제3자가 보증하는 전자 문서입니다. 보통 브라우저는 인증서가 이상하면 경고 화면을 띄웁니다. 사용자는 「계속 진행」을 눌러 넘어갈 수 있습니다. 알려진 HSTS 호스트에서는 경고의 종류와 상관없이 그 버튼 없이 연결을 끊어야 합니다.
이 규칙은 경고를 넘기는 버튼이 공격의 통로가 되기 때문에 있습니다. 공격자가 가짜 인증서를 내밀었을 때 사용자가 무심코 넘기면 암호화가 소용없어집니다.
만료 시각은 헤더를 받을 때마다 새로 셉니다. 사이트에 드나들 때마다 만료 시각이 뒤로 밀리므로
자주 가는 사이트는 정책이 끊기지 않습니다. max-age=0 을 받으면 브라우저는 그 호스트를 더는
알려진 HSTS 호스트로 다루지 않습니다.
stateDiagram-v2
state "모르는 호스트" as unknown
state "알려진 HSTS 호스트" as known
[*] --> unknown
unknown --> known: HTTPS 응답에서 헤더를 받음
known --> known: 헤더를 다시 받아 기간을 새로 셈
known --> unknown: 기간이 끝나거나 max-age=0 을 받음
첫 방문의 빈틈과 프리로드 목록
이 소절은 HSTS 가 스스로 못 막는 한 번과 그 한 번을 메우는 내장 목록을 봅니다.
HSTS 는 브라우저가 헤더를 한 번 받아야 작동합니다. 헤더를 한 번도 받은 적 없는 첫 방문에서 브라우저는 아직 정책을 모릅니다. 기간이 끝난 브라우저도 마찬가지입니다. 이때 나가는 평문 요청은 앞에서 본 SSL 스트리핑에 열려 있습니다.
이 빈틈을 줄이려고 브라우저는 HSTS 호스트 목록을 프로그램 안에 넣어 배포합니다. 이 목록을 HSTS 프리로드 목록이라고 부릅니다. 목록에 오른 도메인은 처음 방문하는 브라우저도 이미 HTTPS 전용으로 압니다.
목록은 구글이 운영합니다. 주요 브라우저가 이 목록을 함께 씁니다. 이 목록은 HSTS 표준 밖에서 브라우저들이 맞춰 쓰는 관례입니다.
목록에 오르려면 사이트가 보내는 헤더가 세 가지를 갖춰야 합니다.
| 지시어 | 조건 |
|---|---|
max-age |
31536000 초, 곧 1년 이상 |
includeSubDomains |
헤더에 적혀 있다 |
preload |
헤더에 적혀 있다 |
셋을 다 갖춘 헤더는 이런 꼴입니다. 기간을 2년으로 잡은 예입니다.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
되돌리기 어려운 정책
이 소절은 HSTS 를 켠 뒤에 생기는 제약을 봅니다. 정책을 기억하는 쪽이 서버가 아니라 브라우저라서 생기는 일입니다.
서버에서 헤더를 뺀다고 정책이 바로 풀리지 않습니다. 사이트가 HTTPS 를 그만두거나 인증서가 만료되면, 정책을 기억한 브라우저는 기간이 끝날 때까지 그 사이트에 들어가지 못합니다. 경고를 넘어가는 버튼도 없습니다.
정책을 거두려면 max-age=0 을 HTTPS 응답에 실어 보내야 합니다. 이 응답은 사이트를 다시 찾은
브라우저에게만 닿습니다. 한동안 안 온 브라우저는 옛 정책을 기간 끝까지 들고 있습니다.
includeSubDomains 는 사고의 범위를 넓힙니다. 사내 도구처럼 HTTP 로만 떠 있는 서브도메인이
하나라도 있으면 그곳이 브라우저에서 막힙니다. 그래서 켜기 전에 서브도메인 전부가 HTTPS 를
제공하는지 확인합니다.
이런 까닭에 흔히 max-age 를 짧게 잡아 시작합니다. 문제가 없는 것을 확인한 뒤 기간을 늘립니다.
서브도메인 포함과 프리로드 신청은 마지막에 더합니다.
헤더를 붙이는 서버 계층
이 소절은 백엔드 구성에서 이 헤더를 어느 계층이 붙이는지 봅니다. 기준은 HTTPS 연결을 누가 받느냐입니다.
흔한 구성에서는 웹 서버·리버스 프록시·로드 밸런서 같은 앞단이 브라우저가 맺은 암호화 연결을 풉니다. 뒤쪽 애플리케이션에는 평문으로 요청을 넘깁니다. 이렇게 앞단에서 암호화를 푸는 일을 TLS 종료라고 부릅니다.
이런 구성에서는 HSTS 헤더를 앞단에서 붙이는 일이 많습니다. 애플리케이션 코드에서 붙여도 됩니다. 브라우저가 받는 것은 앞단을 거쳐 HTTPS 로 도착한 응답이기 때문입니다.
서버는 브라우저가 평문 연결로 받는 HTTP 응답에 이 헤더를 싣지 않아야 합니다. 실어도 브라우저가 버립니다. 평문 요청에는 HTTPS 로 가는 리다이렉션만 돌려줍니다.
앞단과 애플리케이션 사이의 평문 구간은 이 규칙과 상관이 없습니다. 그 구간의 응답은 앞단을 거쳐 HTTPS 로 브라우저에 닿기 때문입니다.
관련 항목
이것을 정의하고 떠받치는 표준·문서
RFC 6797 · RFC 2119 · HTTP · HTTPS · TLS
이 헤더를 이루는 지시어
max-age · includeSubDomains · preload · 지시어
이 정책이 막으려는 공격
중간자 공격 · SSL 스트리핑 · 세션 하이재킹 · DNS 스푸핑 · ARP 스푸핑
첫 방문의 빈틈을 메우는 수단
HSTS 프리로드 목록 · TOFU · HTTPS 레코드 · DNS
HTTPS 전환에 함께 쓰는 장치
리다이렉션 · 혼합 콘텐츠 · 보안 쿠키 · 인증서 · 인증 기관 · 인증서 고정 · Upgrade-Insecure-Requests
한 응답에 같이 실리는 보안 헤더
Content-Security-Policy · X-Frame-Options · X-Content-Type-Options · Referrer-Policy
이 헤더를 붙이는 서버 구성 요소
웹 서버 · 리버스 프록시 · 로드 밸런서 · TLS 종료 · nginx
이 정책을 기억하는 클라이언트와 대상 도메인
브라우저 · 구글 · 사용자 에이전트 · 호스트 · 도메인 이름 · 서브도메인
max-age 라는 이름을 함께 쓰는 헤더와 속성
Cache-Control · Set-Cookie · Max-Age · Access-Control-Max-Age · 쿠키
이것이 속하는 상위 분류
웹 보안 · 헤더 · HTTP 헤더 · 보안 · 응답 헤더
다른 이름: HTTP Strict Transport Security · Strict-Transport-Security · HTTP 엄격 전송 보안