2008년 여름, 열여섯 명이 마이크로소프트 레드먼드 캠퍼스의 한 방에 모여 인터넷을 패치할 날짜를 조용히 정했다.
시스코에서, 마이크로소프트에서, 지구상 거의 모든 네트워크가 의존하는 DNS 소프트웨어를 만드는 팀에서 온 사람들이었다. 세계에서 가장 널리 쓰이는 DNS 서버 BIND를 이십 년 가까이 이끌어 온 Paul Vixie가 거기 있었다. 그리고 그들 모두를 그 방에 불러 모은 물건을 찾아낸, IOActive의 젊은 연구자 Dan Kaminsky도 있었다. 카민스키가 가진 것은, 사실상 아무 도메인이든 — 당신의 은행, 이메일 제공자, 노트북이 신뢰하는 소프트웨어 업데이트 서버든 — 그 이름 해석을 몇 초 만에, 옆에서, 암호 키 하나 깨지 않고 통째로 가로채는 방법이었다. 깰 키가 없었기 때문이다. DNS에는 키가 없었다.
놀라운 대목은 버그가 존재했다는 게 아니다. 그해 7월 모두가 배포한 그 ‘해결책’이 사실은 해결이 아니었다는 것이다. 그것은 시간 벌기였다.
16비트짜리 자물쇠
이 소동을 이해하려면, DNS의 보안이 얼마나 우스울 만큼 얄팍했는지 — 그리고 상당 부분 지금도 그런지 — 를 알아야 한다.
리졸버가 권한 서버에 “example.com의 주소가 뭐냐”고 물을 때, UDP 패킷 하나를 보내고 응답을 기다린다. 응답을 그 질문에 묶어주는 유일한 끈은 16비트짜리 트랜잭션 ID — 리졸버가 고르고 응답이 그대로 되돌려줘야 하는 숫자다. 16비트는 65,536가지다. 그게 전부다. 이 숫자와, 응답이 올바른 UDP 포트로 도착해야 한다는 사실이, 누군가 응답을 위조하는 것을 막는 방어의 전부였다.
고전적인 공격은 진짜 서버와 경주하는 것이었다: 추측한 트랜잭션 ID를 단 위조 응답을 폭풍처럼 쏘아, 하나가 정품 응답보다 먼저 도착하기를 바란다. 쉬워 보이고, 절반은 실제로 쉬웠다 — 하지만 이걸 버틸 만하게 만든 함정이 하나 있었다. 진짜 응답이 도착하면 리졸버가 그것을 캐시하고, 레코드의 TTL 동안은 다시 묻기를 멈춘다. 잘못 추측하면, 캐시가 만료될 때까지 — 때로는 몇 시간, 때로는 하루 — 기다려야 그 이름에 대한 다음 기회가 왔다. TTL당 한 번의 경주. 확률이 충분히 나빴고 재시도 간격이 충분히 느려서, DNS는 이걸로 25년을 절뚝이며 버텼다.
카민스키가 실제로 찾아낸 것
카민스키의 통찰은, 원하는 이름을 공격하기를 그만두는 것이었다.
한 번 정상적으로 캐시되면 몇 시간 잠기는 레코드인 www.bank.com을 두고 경주하는 대신, 그는 피해 리졸버가 캐시될 수 없는 이름들을 조회하게 했다 — 존재하지 않는 이름이니까: aaa.bank.com, 다음은 aab.bank.com, 다음은 aac.bank.com, 끝없이. 이들 하나하나는 리졸버가 처음 보는 새 질의라서, 그것을 막을 게 캐시에 없다. 기다림도 없다. 그는 초당 수천 번 경주를 돌릴 수 있었다.
그다음이 두 번째 절반, 성가신 일을 재앙으로 바꾼 대목이다. 경주에서 이기면 — 위조 응답 하나가 진짜를 이기면 — 그는 aac.bank.com에 대한 질문만 답하지 않았다. 위조 패킷의 권한(authority) 섹션에 이런 취지의 레코드를 채워 넣었다: “그리고 참고로, bank.com 전체의 네임서버는 여기, 내가 통제하는 이 IP다.” DNS는 이를 in-bailiwick 참조라 부르고, 리졸버들은 그것이 해당 존을 대변할 자격 있는 서버에서 온 것처럼 보였기에 받아들였다. 의미 없는 서브도메인 하나에 대한 경주를 한 번 이기면, 레코드 하나를 오염시키는 게 아니라 — 도메인 전체의 네임서버를 갈아치웠다. 이제 bank.com 아래 모든 것이 공격자가 말한 곳으로 해석됐다.
기다릴 TTL도 없고, 무찌를 암호도 없었다. 위조 UDP 패킷이 충분히 많으면 됐고, 괜찮은 회선에서 그건 몇 초였다.
시간을 산 패치
이 이야기를 다시 할 가치가 있는 이유가 여기 있다. 2008년 7월 8일 배포된 다중 벤더 패치 — 마이크로소프트, ISC의 BIND, 시스코, 레드햇, 그 외 다수가 사전 합의로 같은 날 릴리스했다 — 는 DNS에 인증을 더하지 않았다. 그럴 수 없었다. 인터넷의 모든 리졸버에 진짜 암호학적 검증을 조율된 한 방으로 소급 이식할 길은 없었다.
패치가 한 일은 공격자가 추측할 숫자를 하나 더한 것이었다. 카민스키 이전에는 대부분의 리졸버가 모든 질의를 하나의 예측 가능한 UDP 소스 포트에서 보냈고, 그래서 변수는 16비트 트랜잭션 ID뿐이었다. 패치는 리졸버가 소스 포트도 무작위화하게 만들었다 — 대략 16비트의 엔트로피가 더 붙는다. 이제 위조 응답은 트랜잭션 ID 와 올바른 포트를 둘 다 맞혀야 했고, 탐색 공간은 약 65,000에서 수십억으로 밀려났다. 공격이 불가능해진 게 아니다. 잘 패치된 리졸버를 상대로 비현실적일 만큼 느려진 것이다. 그건 다른 얘기이고, 관련된 모두가 그걸 알았다.
소스 포트 무작위화는 0x20 인코딩과 같은 계열의 임시방편이다 — 질의하는 이름의 글자를 무작위로 대문자화해 응답이 그 정확한 조합을 그대로 되돌려주게 하는 기법. 둘 다, 작정한 거짓말쟁이에 저항하도록 설계된 적 없는 프로토콜에서 예측 불가능성을 몇 비트 더 짜낸다. 위조의 비용을 높일 뿐, 누가 진실을 말하는지를 세우지는 못한다.
유출
공개 과정에도 나름의 드라마가 있었고, 조율된 비밀 유지가 얼마나 어려운지에 대한 작은 교훈이다.
카민스키는 보안 커뮤니티에 드문 것을 요청했다: 호기심을 30일만 눌러 두고, 조용히 패치하라, 8월 Black Hat에서 전부 설명하겠다. 대부분은 응했다. 그러다 7월 21일, 리버스 엔지니어 Thomas Dullien — Halvar Flake라는 이름으로 더 알려진 — 이 오직 패치의 모양과 반응의 크기만 보고 그럴듯한 메커니즘을 자기 블로그에서 추론해 냈다. 그는 본질적으로 맞혔다. 몇 시간 안에, 진짜 세부를 브리핑받았던 보안 회사 Matasano가 그의 추측을 확인하는 글을 올렸다. 몇 분 만에 내렸지만, 인터넷에는 실행 취소 버튼이 없다. 완전한 기법은 카민스키의 8월 6일 Black Hat 발표 약 2주 전에 새어 나갔고, 그 무렵 발표 내용은 이미 청중에게 유출된 뒤였다.
이 취약점은 이름을 얻었고 — CVE-2008-1447 — 이후 인터넷 규모의 진짜 아슬아슬한 위기라는 지위를 얻었다.
우리는 결국 고치지 않았다
불편한 후일담은, 진짜 해결책은 DNSSEC이어야 했다는 것이다. DNSSEC는 DNS 레코드에 서명해, 리졸버가 그것이 존의 실제 소유자에게서 왔고 전송 중 위조되지 않았음을 검증하게 해준다. DNSSEC는 2008년에도 존재했다. 지금도 존재한다. 채택률은 세는 방식에 따라 여전히 전체 존의 3분의 1 언저리 아래이고 — 리졸버 쪽 검증은 그보다 더 듬성듬성하다. 카민스키 공격에 대한 암호학적 답은, 우리가 포트 무작위화 임시방편에 기대 온 그 시간 내내 선반 위에 놓여 있었다.
그래서 정직한 요약은 이렇다: 2008년 연구자 한 명이 인터넷의 어떤 이름이든 마음대로 위조할 수 있음을 입증했고, 벤더들로 가득 찬 방이 16비트의 추가 무작위성으로 인터넷에 시간을 사줬으며, 그런 다음 업계 대부분은 진짜 자물쇠 설치를 거절했다. 카민스키 공격은 패치하고 잊어버린 역사 속 호기심거리가 아니다. 그것은 오늘 당신의 리졸버가 소스 포트를 무작위화하는 이유다 — 수십억 조합을 충분히 빠르게 추측하는 게 성가시다는 이유 하나로 여태 서 있는 25년 된 설계, 그리고 우리가 조용히, 성가신 정도면 충분하다고 합의한 결과.