HPKP: 너무 위험해서 폐기된 보안 헤더

HTTP Public Key Pinning은 헤더 하나로 사이트의 TLS 키를 고정하게 해줬다. 동시에 내 도메인을 영구히 벽돌로 만들거나, 공격자가 인질로 잡게도 했다. 브라우저가 죽였다.

대략 2015년부터 2018년까지, HTTP 응답 헤더 하나를 사이트에 붙였다가 살짝만 틀리면 돌아오는 방문자 전원을 내 도메인에서 최대 1년간 잠가버릴 수 있던 시기가 있었다. 페이지 하나가 404 뜨는 정도가 아니다. 브라우저에게 “내 사이트를 거부하라” — 유효한 인증서를 단 진짜 사이트를 — 고 시키고, 내가 미리 걸어둔 타이머가 끝날 때까지 계속 거부하게 만드는 헤더다.

그 헤더가 HPKP, HTTP Public Key Pinning이다. 내가 아는 한, 막으려던 위협보다 그 자체가 더 위험했던 보안 기능의 가장 선명한 사례다.

무엇을 고치려 했나

HPKP가 겨눈 문제는 실재하고, 고약하다. 브라우저는 약 150개의 인증기관(CA)을 신뢰하고, 그중 어느 하나든 아무 도메인에 대해 인증서를 발급할 수 있다. 이게 CA 체계 전체의 구조적 약점이다: 당신 은행 도메인에 대해 멀쩡해 보이는 인증서를 찍어내는 데는 손상되거나 부주의한 CA 하나면 충분하다. 실제로 벌어졌다. 2011년 네덜란드 CA DigiNotar가 뚫려 Google 등에 대한 위조 인증서가 발급됐고, 이란에서 트래픽 가로채기에 쓰였다. 그 인증서들은 브라우저 눈에는 완벽히 유효했다.

피닝이 그 답이었다. 150개 CA 아무거나 믿는 대신, 브라우저에게 이렇게 말한다: 도메인에는 오직 이 특정 공개 키(또는 소수의 집합)만 받아라. 2015년 발행된 RFC 7469는 그 지시를 헤더로 실어 보내게 해줬다:

Public-Key-Pins: pin-sha256="base64=="; pin-sha256="backup==";
    max-age=5184000; includeSubDomains

브라우저는 첫 방문 때 이걸 보고, 고정된 키를 max-age초 동안 기억하고, 그 뒤로는 그 집합에 없는 키를 가진 인증서라면 — 신뢰받는 CA가 서명했더라도 — 당신 도메인에 대해 거부한다. 손상된 CA가 찍은 위조 인증서는 당신 핀과 안 맞으니 공격은 실패한다. 종이 위에서는 구멍이 완전히 메워진다.

아무도 안전하게 쥘 수 없던 지뢰

그 메커니즘을 다시 읽어보면 문제가 튀어나온다. 브라우저는 핀을 기억하고, 안 맞는 인증서를 거부하고, 내가 시킨 기간만큼 둘 다 한다. 사용자가 클릭해서 넘어가는 우회는 없다 — 있었다면 이 기능은 무의미하다. 공격자가 그냥 “클릭해서 넘어가세요”라고 하면 되니까. 그럼 고정해 둔 키가 사라지면 무슨 일이 벌어질까?

사이트가 벽돌이 된다. 여기엔 이름까지 붙었다: HPKP suicide. 키를 교체하고 핀을 잊는다. 서버가 뚫려 키를 도둑맞아서 어쩔 수 없이 교체해야 한다. 백업 핀은 그것마저 잃어버린 키의 것이었다. 이 중 하나라도 벌어지면, 당신 헤더를 한 번이라도 본 모든 브라우저가 이제 당신의 새롭고 유효하고 올바르게 발급된 인증서를 — 옛 핀과 안 맞는다는 이유로 — 거부하고, max-age가 만료될 때까지 계속 거부한다. 새 인증서를 사도 못 고친다. CA에 말해도 못 고친다. 타이머가 끝나기를 기다리며, 끌 수도 없는 에러 페이지를 돌아온 방문자들이 마주하는 걸 지켜본다. RFC는 현재 체인에 없는 키에 대한 백업 핀을 최소 하나 요구해 이걸 막으려 했지만, 잃어버렸거나 애초에 제대로 만들지 못한 백업 핀은 아무것도 지켜주지 못한다.

그러다 연구자들이 거울상을 발견했고, 그쪽이 더 나빴다. RansomPKP라 부르자. 당신 서버를 잠깐 장악한 공격자 — 짧은 침해, 탈취된 서브도메인 — 는 오래갈 무언가를 훔칠 필요가 없다. 그냥 HPKP를 켜고, 자기가 통제하는 키를 긴 max-age로 고정하면 된다. 이제 당신이 그를 쫓아내고 전부 복구한 뒤에도, 침해 동안 페이지를 한 번이라도 연 방문자들은 당신 도메인을 공격자의 키에 고정한 상태다. 당신의 정당한 인증서가 그들의 핀에서 실패한다. 당신은 걸어둔 적 없는 헤더 때문에 자기 사이트에서 잠겼고, 공격자는 키를 팔겠다고 제안한다. 수년간 HPKP 채택률을 추적한 Scott Helme는 결국 이 기능을 죽여야 한다고 주장하게 됐다. 정확히, 스스로를 이렇게까지 심하게 해치는 방법이 안전하게 쓰는 방법보다 많았기 때문이다.

거의 아무도 안 썼고, 그게 답이었다

논쟁을 끝낸 숫자가 여기 있다. 정점에서 HPKP는 상위 100만 사이트 중 약 3,500곳에 깔렸다 — 0.3퍼센트 남짓. 그리고 줄고 있었다. 겁먹거나 아슬아슬한 사고를 겪은 사람들이 조용히 다시 꺼서 650곳 쪽으로 내려갔다. 어떤 보안 기능이 수년째 쓸 수 있는데도, 그걸 가장 잘 다룰 사람들 — 인터넷 상위 천 개 사이트를 운영하는 이들 — 이 압도적으로 거부한다면, 그건 무관심이 아니다. 집단적 위험 평가다. 실수의 폭발 반경은 벽돌이 된 도메인이었고, 대가는 그 무렵 이미 더 나은 답이 나온 공격에 대한 방어였다.

HPKP가 실패하는 동안 Certificate Transparency는 작동하고 있었으니까. CT는 같은 위협을 다른 각도로 친다: 사이트마다 개별적으로 키를 고정하고 절대 잃지 않기를 기도하는 대신, CA가 발급하는 모든 인증서를 공개된, 추가만 가능한, 감시되는 로그에 남기게 한다. 당신 도메인에 인증서를 오발급한 CA는 이제 어둠 속에서 그럴 수 없다 — 인증서가 로그에 뜨고, 당신(또는 당신을 대신해 지켜보는 모니터)이 그걸 본다. 어떤 웹사이트에도 제 발을 날려버릴 장치를 장전하라고 요구하지 않으면서 DigiNotar 시나리오를 잡아낸다.

어떻게 끝났나

Chrome 보안팀이 결정을 내렸다. Chris Palmer의 “Intent to Deprecate and Remove: Public Key Pinning”은 단도직입적이었다: 낮은 채택률, 적대적 피닝과 우발적 자기-DoS의 높은 위험, 그리고 이미 자리 잡은 더 나은 도구. Chrome은 2018년 중반 버전 67에서 HPKP를 폐기 예고했고 버전 72에서 완전히 들어냈다. Firefox도 뒤따라 지원을 뺐다. 헤더는 관성으로 몇몇 곳에서 아직 파싱되긴 하지만, 지금 어떤 브라우저도 그걸로 핀을 걸지 않는다. 죽었다.

한동안 공식 대체재는 또 다른 헤더 Expect-CT였다. 인증서가 CT 로그에 기록됐는지 요구하고 위반을 리포트하게 해줬다. 그리고 깔끔한 마지막 아이러니가 왔다: Expect-CT 자신도 몇 년 뒤 폐기됐다 — 실패해서가 아니라, Certificate Transparency 강제가 브라우저 기본값이 됐기 때문이다. 이제 자동으로 얻는 걸 요청하려고 헤더를 달 필요는 없다. HPKP가 시작한 ‘사이트가 설정하는 인증서 정책’이라는 장치 전체가 “브라우저가 알아서 해주고, 당신은 그걸 자기를 겨눈 무기로 바꿀 수 없다”로 무너져 내렸다.

분명히 해두면, 피닝이 사라진 건 아니다. 브라우저는 지금도 소수의 고가치 도메인 — 주로 Google 자체 자산 — 에 대한 정적 핀을 바이너리에 박아 함께 배포하고, google.com의 키를 실수로 잃어버릴 리 없는 사람들이 그걸 관리한다. 무엇이 진짜 잘못됐는지를 알려주는 대목이다. 개념으로서의 피닝은 괜찮았다. 셀프서비스 헤더로서의 피닝 — 안쪽을 겨눈 지뢰와 되돌리기 버튼 없이 모든 사이트 운영자에게 쥐여준 그것 — 이 실수였다. 이 기능의 안전한 버전은 당신이 설정하지 못하는 버전이다.

교훈은 TLS 너머로 일반화된다. 최악의 실패가 막으려던 공격보다 더 나쁜 보안 통제 — 게다가 그 실패가 조용하고, 지연되고, 되돌릴 수 없는 — 는 거친 구석이 있는 강한 통제가 아니다. 보안 통제의 옷을 입은 부채다. HPKP는 좋은 아이디어였지만, 그걸 운영해야 할 사람들과 접촉하고는 살아남지 못했다. 브라우저 벤더들이 그것에게 해준 가장 다정한 일은, 빼앗아 간 것이었다.

토론 참여

← 블로그로 돌아가기