HSTS 프리로드: 당신의 보안을 브라우저 벤더에게 넘기는 일

HSTS 프리로드 목록에 도메인을 올리는 건 일방통행 문이다. 목록은 당신 서버가 아니라 브라우저 바이너리 안에 있어서, 되돌리려면 당신이 통제하지 못하는 크롬 릴리스를 몇 달이고 기다려야 한다 — 프리로드하라고 권하는 사람들이 말 안 하는 부분이다.

HSTS 프리로드 튜토리얼이 절대 안 짚어주는 시나리오가 있다. 1년 전 당신은 Strict-Transport-Security 헤더에 preload를 추가하고, hstspreload.org에 도메인을 제출했고, 그게 크롬 안에 실렸다. 잘했다 — 책임 있는 선택이었다. 그런데 이제 며칠간 평문 HTTP로 서비스해야 할 서브도메인이 생겼다: TLS를 못 하는 임베디드 장비, 레거시 내부 호스트, 통제할 수 없는 파트너의 박스. 당신은 헤더에서 preload 지시어를 뺀다. 제거 요청을 제출한다. 그리고 기다린다. 몇 시간이 아니다. 며칠도 아니다. 몇 달 — “그냥 끄면 되지”가 다음 분기 로드맵의 항목으로 바뀔 만큼.

켜는 건 사소하고 끄는 건 잔인하게 느린 이 비대칭이 HSTS 프리로드의 전부이고, 누군가 프리로드를 “HSTS인데 더 강한 것”이라고 할 때 슬쩍 넘어가는 바로 그 부분이다.

헤더가 하는 일, 그리고 헤더가 못 메우는 구멍

HSTS 자체는 단순하고 좋다. Strict-Transport-Security: max-age=31536000; includeSubDomains는 서버가 HTTPS로 보내는 헤더로, 브라우저에게 이렇게 말한다: 앞으로 1년간 나와 평문 HTTP로 절대 말하지 말고, 사용자가 인증서 경고를 클릭해 넘어가게 두지도 마라. RFC 6797이 2012년에 이걸 표준화했다. 브라우저가 그 헤더를 한 번 보고 나면, 네트워크 공격자는 당신 도메인으로의 연결을 다운그레이드하거나 TLS를 벗겨낼 수 없다. 브라우저가 HTTP를 시도조차 거부하니까.

그 문단에서 일하는 단어는 보고다. HSTS는 최초 사용 시 신뢰(trust-on-first-use)다. 보호는 브라우저가 좋은 연결로 헤더를 적어도 한 번 받은 뒤에야 존재한다. 브라우저가 당신 도메인에 보내는 맨 처음 요청은 — 헤더를 받기 전이라 — 여전히 HTTP로 나갈 수 있고, 그 첫 요청이야말로 경로에 앉은 공격자가 가로채고, 리다이렉트하고, 피해자를 영원히 HTTP에 묶어둘 수 있는 지점이다. RFC 6797은 보안 고려사항에서 이걸 정직하게 이름 붙인다: 부트스트랩 MITM 취약점. 헤더는 정작 그 헤더를 전달해야 할 연결을 보호하지 못한다.

프리로드는 바로 그 한 구멍에 대한 해법이다. 브라우저가 당신 도메인이 HTTPS 전용이라는 걸 이미 알고 출하되면, 가로챌 안전하지 않은 첫 요청 자체가 없다. 사용자가 당신을 한 번도 방문하기 전에 보호가 자리잡는다.

함정: 그건 당신의 목록이 아니다

여기서 아무도 똑똑히 말 안 하는 게 있다. 프리로드 목록은 DNS 항목이나 HTTP 헤더처럼 당신이 발행하고 통제하는 레코드가 아니다. 그건 브라우저 안의 파일이다. 크로미움에서 그건 말 그대로 소스 파일 — net/http/transport_security_state_static.json — 로, 바이너리에 컴파일돼 모든 릴리스와 함께 출하된다. 파이어폭스, 사파리, 엣지는 본질적으로 같은 크로미움 관리 목록을 가져다 쓴다. RFC 6797은 이를 예견했지만(§12.3이 브라우저 벤더가 “루트 CA 인증서가 내장되는 방식과 비슷하게” 구성하는 HSTS 호스트 “프리로드 목록”을 설명한다), preload 지시어나 제출 절차를 표준화하진 않았다. 당신 헤더의 그 토큰과 hstspreload.org 사이트는 프로토콜 기능이 아니라 구글이 운영하는 사실상의(de facto) 메커니즘이다. 어떻게 올라가고 어떻게 내려오는지를 규정한 RFC는 없다.

그래서 프리로드할 때, 당신은 자기 인프라를 설정하는 게 아니다. 남의 소프트웨어 안의 데이터 파일에 패치를 제출하고, 그걸 수십억 명에게 출하해달라고 요청하는 것이다. 그리고 승인 요건은 의도적으로 엄격하다: 유효한 인증서, 최소 1년(31536000초)의 max-age, includeSubDomains, preload 토큰, 같은 호스트에서의 HTTP→HTTPS 리다이렉트. 합리적인 관문이다. 동시에 그건 당신 도메인 트리 전체를 대신해, 무기한, 릴리스 일정에 당신이 한마디도 못 하는 상대와 맺는 계약이다.

무서워해야 할 부분은 제거다

추가가 빠른 건 관문이 “당신은 이미 HTTPS 전용임을 증명하라”뿐이기 때문이다. 제거가 느린 건 목록이 출하되는 방식 때문이다. 내려오려면 먼저 사이트를 떠날 자격이 있게 만들어야 한다: preload를 더는 담지 않은 유효한 HSTS 헤더를 제공해야 한다(HSTS를 그냥 뜯어내면 안 된다 — 헤더를 아예 안 주는 도메인은 제거 대상이 아니다). 그다음 제거 요청을 제출한다. 그다음 항목이 미래의 빌드에서 빠진다. 그다음 그 빌드가 배포돼야 한다. 그다음 사용자가 실제로 브라우저를 업데이트해야 한다.

그 마지막 단계가 killer다. 목록은 브라우저 업데이트를 통해 사용자에게 닿고, 아직 업데이트 안 한 브라우저에 닿을 방법은 — 전혀 — 없다. hstspreload.org의 제거 안내 페이지조차 변경이 대부분 사용자에게 닿기까지 몇 달을 예상하라고 말하는데, “대부분”은 “전부”가 아니다. 옛 빌드를 돌리는 사람은 옛 목록을 계속 강제한다. 오래된 설치 파일에서 새로 깐 브라우저는 그 설치 파일이 만들어진 시점의 목록을 가져온다. 실질적으로, 도메인 프리로드는 영구적이고, 당신은 1년보다 짧은 어떤 시간 안에도 그 이름이나 그 아래 어떤 이름으로도 평문 HTTP를 서비스할 일이 절대 없으리라는 데 베팅하는 것이다.

그리고 includeSubDomains는 — 프리로드가 이걸 요구하므로 — 그 베팅을 사람들이 깨닫는 것보다 더 크게 만든다. 당신은 example.com에 대해 HTTPS를 약속하는 게 아니다. example.com 아래 앞으로 존재할 모든 호스트에 대해, 영원히, 아직 아무도 안 만든 것까지 포함해 약속하는 것이다. 다른 부서의 누군가가 TLS를 종료하지 못하는 박스 위에 legacy-vendor-portal.example.com을 세우는 날, 그건 표준 준수 브라우저에서 그냥 도달 불가가 되고, 해법은 상위 도메인 전체에 대한 몇 달짜리 제거 절차다.

당신이 실제로 하는 거래

조심스럽게 말하고 싶다. 프리로드는 실수가 아니니까. HTTPS 전용이고 앞으로도 그럴 작정인 도메인 — 은행, 주력 제품, 부트스트랩 MITM이 실제 위협인 무엇이든 — 에게 그건 진짜 구멍을 막아주고, 마땅히 해야 한다. 요점은 “프리로드하지 마라”가 아니다. 요점은 그걸 공짜 업그레이드로 여기지 말고 거래를 똑똑히 보라는 것이다.

얻는 것은 첫 방문 구멍을 막는 일이다. 포기하는 것은 당신만의 일정으로 마음을 바꿀 능력이다. 당신은 당신 도메인의 HTTPS 강제에 관한 결정을 — 몇 초면 바꿀 수 있는 — DNS와 서버 밖으로 옮겨, 브라우저 벤더가 관리하는 바이너리 안으로 — 회계 분기 단위로 바꾸는 곳으로 — 넣었다. 부트스트랩 취약점도 사라지지 않았다, 옮겨갔을 뿐이다. 예전엔 최초 사용 시 신뢰였고, 사용자의 생애 첫 방문에서 악용 가능했다. 이제는 최초 업데이트 시 신뢰 — 구멍은 아직 어떤 출하 목록에도 없는 갓 만든 도메인과, 몇 달 묵은 빌드를 돌리는 브라우저의 몫이다. 더 작은 창, 그래도 진짜 창.

이건 루트 CA 저장소와 같은 거래이고, 거기서 우리가 그걸 받아들이는 것과 같은 이유로 받아들인다: 신뢰를 브라우저로 중앙화하는 건 웹 전체로 확장되는 유일한 방법이다. 동작한다. 하지만 “동작한다”와 “공짜다”는 다른 주장이고, 프리로드 목록은 첫째일 뿐인데 둘째인 것처럼 팔린다. 그 지시어를 추가하기 전에, 튜토리얼이 건너뛰는 질문을 던져라: 나는 이 이름과 그 아래 모든 것에 대해, 내가 계획할 수 있는 것보다 더 오래 HTTPS 전용일 준비가 됐는가? 그렇다면 자신 있게 프리로드하라. 확신이 안 선다면, 정직한 수는 preload 토큰 없이max-age부터 돌리는 것이다 — 재방문자에게는 거의 모든 보호를 얻으면서, 당신 문의 열쇠는 당신이 쥔다.

토론 참여

← 블로그로 돌아가기