2017년 4월, Xudong Zheng이라는 연구자가 apple.com을 등록했다.
물론 진짜 그 도메인은 아니다. 그런데 그해 봄 Chrome, Firefox, Opera에서 그가 만든 페이지를 열면 주소창에는 apple.com이 떴고, 연결은 HTTPS였으며, 인증서는 유효하고 초록색이었다. 눈을 부릅뜨고 찾아낼 오타 같은 건 없었다. 올바른 글자가 올바른 순서로 박혀 있었다. URL을 한 글자씩 소리 내어 읽어도 끝내 눈치채지 못한다.
그 글자들은 전부 키릴 문자였다. 그가 실제로 등록한 도메인은 xn--80ak6aa92e.com이고, 브라우저가 그려낸 аррӏе.com은 키릴 а(U+0430), 키릴 р(U+0440), 그리고 소문자 L 자리를 차지한 팔로치카(palochka)로 조립된 것이었다. 폰트가 보기엔 apple이다. DNS가 보기엔 완전히 다른 이름이다. 주소창을 보는 당신이 보기엔 — 차이가 전혀 없다.
이게 호모그래프 공격이다. 그런데 불편한 지점은 이 속임수 자체가 아니다. 이 속임수가 인터넷이 의도적으로 내린 두 가지 결정에서 곧장 떨어져 나온다는 것, 그리고 그중 어느 쪽도 깔끔하게 되돌릴 방법이 없다는 것이다.
DNS는 끝내 읽는 법을 배우지 못했다
DNS는 ASCII만 쓴다. 알파벳, 숫자, 하이픈 — 이른바 LDH 집합, 그게 전부다. 웹이 미국 것이고 이름이 영어였던 시절엔 괜찮았다. 그러다 서울이나 카이로, 아테네의 누군가가 자기 문자로 된 도메인을 원하는 순간 재앙이 됐다.
그 해법이 IDNA로 표준화됐고, 그 핵심에는 퓨니코드(Punycode, RFC 3492)라는 꽤 영리한 엔지니어링이 있다. 퓨니코드는 가역적이고 결정적인 인코딩이다. 유니코드 문자열을 받아 DNS가 실어 나를 수 있는 순수 ASCII 문자열로 바꾸고, 소프트웨어가 디코딩 대상임을 알도록 앞에 xn--를 붙인다. münchen.com은 회선 위에서 xn--mnchen-3ya.com이 된다. 리졸버도, 레지스트리도, TLS 계층도 전부 이 ASCII 형태만 다룬다. 오직 브라우저가, 맨 마지막 단계에서, 그걸 다시 유니코드로 디코딩해 주소창에 münchen을 그려 사람에게 보여준다.
이건 올바른 설계다. 지구상의 모든 DNS 서버를 UTF-8 처리하도록 다시 짤 수는 없으니, 가장자리에서 인코딩하고 핵심은 건드리지 않는 것이다. 출시됐고, 작동하고, 당신도 알아채지 못한 채 거의 틀림없이 써봤다.
문제는 디코딩해서-보여주는 그 마지막 단계다. 브라우저의 임무는 사용자에게 친절한 유니코드 이름을 보여주는 것이다. 그리고 유니코드는, 설계상, 다른 글자와 똑같이 생긴 글자를 수천 개 품고 있다.
유니코드는 쌍둥이로 가득하다
라틴 a, 키릴 а, 그리스 알파 α는 보통의 폰트가 같은 모양으로 그려내는 서로 다른 세 개의 코드 포인트다. 이건 유니코드의 버그가 아니다. 유니코드는 세계의 문자 체계를 담고 있고, 세계의 문자 체계는 실제로 글리프를 공유한다 — 라틴과 키릴 둘 다 그리스에서 글자를 물려받았으니, o가 о이고 о가 ο인 건 당연한 일이다. 유니코드는 이걸 혼동 문자(confusables)라 부르고, 그것만 다루는 기술 표준(UTS 39)을 통째로 내놓았다. 만든 사람들은 무슨 일이 닥칠지 정확히 알고 있었기 때문이다.
그래서 이제 전체 유니코드 범위를 받아들이는 이름 체계, 그걸 충실히 렌더링할 의무가 있는 표시 계층, 그리고 수십 개의 글자가 다른 문자에 완벽한 닮은꼴을 가진 문자 집합 — 이 셋이 갖춰졌다. 호모그래프 공격은 떨어뜨린 공처럼 이 세 가지 사실에서 굴러떨어진다. 결함을 파고드는 게 아니다. 그냥 유명한 이름을 남의 알파벳으로 철자할 뿐이다.
그리고 사람들은 이걸 거의 곧바로 알아챘다. Gabrilovich와 Gontmakher는 2002년 2월 Communications of the ACM에서 이 공격을 기술했다 — 그들은 키릴 문자로 된 microsoft.com을 등록해 작동을 증명했다. 2005년 ShmooCon에서 Eric Johanson은 키릴 pаypal.com을 시연했다. 2017년 화제가 된 apple.com은 이미 15년째 상연 중이던 연극의 3막이었다.
그래서 브라우저가 맞춤법 검사기가 됐다
DNS가 이걸 단속하지 않고 레지스트리들도 합의하지 못하니, 브라우저가 마지막 방어선이 됐다. 즉 브라우저는 모든 호스트명의 모든 레이블마다, 당신이 곧 보게 될 유니코드가 진짜 이름인지 변장인지를 추측해야 한다.
Chrome의 답은 휴리스틱 더미인데, 그 규칙들을 읽어보면 이 문제가 얼마나 어려운지 드러난다. Chrome은 각 레이블을 디코딩한 뒤, 여러 경보 중 하나라도 울리면 예쁜 유니코드 대신 날것의 xn-- 퓨니코드를 보여준다. 레이블이 수상한 방식으로 문자 체계를 섞었거나(라틴에 키릴이 붙음), 모든 혼동 문자를 정규형으로 매핑해 얻은 “스켈레톤”이 알려진 인기 도메인과 일치하거나, 또는 해당 문자 체계용 최상위 도메인과 맞지 않는데도 전부 닮은꼴 글자로만 이뤄졌을 때다. 마지막 규칙이 바로 Zheng의 аррӏе.com이 뚫은 지점이다. 그건 순수 키릴, 단일 문자 체계, 내부적으로 일관됐다. 브라우저들이 2005년 소동 이후 기대온 혼합-문자체계 탐지기는 신고할 거리를 못 찾았다. 섞인 게 없었으니까. Chrome은 바로 이 구멍을 막으려고 58 버전에서 전체-문자체계 혼동 탐지를 추가했다.
Firefox는 다른 선택을 했는데, 곱씹어볼 가치가 있다. 명백히 틀린 게 아니기 때문이다. Firefox에는 network.IDN_show_punycode라는 설정이 있어서, 모든 국제화 이름을 날것의 xn-- 알아볼 수 없는 글자로 표시하도록 강제할 수 있다. 기본값은 꺼짐이다. Mozilla는 이걸 켜기를 거부했고, 2017년 이후 사람들이 요청했을 때도 다시 거부했다. 이걸 켜면 인터넷상의 모든 정상적인 비라틴 도메인 — 모든 키릴, 아랍, 중국어, 데바나가리 이름 — 이 읽을 수 없는 ASCII 죽으로 변하기 때문이다. 키릴 스푸핑으로부터 영어 사용자를 보호하겠다고 정작 키릴로 글을 쓰는 모든 사람의 주소창을 망가뜨리는 건, 안전에 대한 이상한 정의다. 그래서 그들은 대신 레지스트리별 규칙과 혼동 검사에 기대고, 보호가 더 무르다는 걸 받아들인다.
이 논쟁엔 결론이 없다. 그게 핵심이다. 호모그래프에 대한 모든 방어는 국제화에 매기는 세금이고, 국제화에 대한 모든 양보는 호모그래프에게 열어주는 틈이다. 두 가지 좋은 것을 서로 맞바꾸는 거래이며, 둘 다 이기는 설정값은 없다.
끝내 고쳐지지 않는 부분
브라우저 휴리스틱은 정말로 좋아졌고, 이 공격의 쉬운 버전은 이제 현대 주소창에서 대체로 살아남지 못한다. 스켈레톤 매칭이 뻔한 표적을 잡고, 문자 체계 규칙이 게으른 시도를 잡는다.
하지만 휴리스틱은 트렌치코트를 걸친 차단 목록이고, 그 탐색 공간은 유니코드 혼동 문자 표 전체에 사칭할 가치가 있는 모든 브랜드를 곱한 크기다. 아직 아무도 규칙을 안 쓴 어떤 조합 — 어떤 문자 체계 짝, 덜 유명한 어떤 표적, 새로 배정된 어떤 코드 포인트 — 은 언제나 존재할 것이다. 2017년 공격이 통한 건 모두의 머릿속 모델이 “혼합 문자 체계를 조심하라”였고, 순수 키릴이 그 모델을 그대로 통과했기 때문이다. 다음 공격도 같은 식으로 통할 것이다. 우리가 쓴 규칙과 그 규칙이 의미하려던 것 사이의 틈을 파고들 것이다.
당신은 URL을 왼쪽에서 오른쪽으로 읽고 눈을 믿도록 배웠다. 이 게임의 전부는, 당신의 눈이 애초에 URL을 읽은 적이 없다는 데 있다. 눈은 URL의 그림을 읽고 있었다.