DNS가 답할 수 없는 질문이 하나 있다: co.uk는 웹사이트인가?
당신은 아니라는 걸 안다 — 거기는 영국 회사들이 한 단계 아래에 자기 이름을 등록하는 곳이니까. 하지만 DNS 프로토콜엔 그렇게 적혀 있지 않다. co.uk는 이름 트리 안의 지극히 평범한 노드이고, 구조적으로 google.com과 똑같다. 둘 다 레코드가 있고, 둘 다 해석되고, 둘 다 두 레이블 깊이에 있다. 점 개수로 구분하려 들면 example.com(점 하나, 등록 가능), example.co.uk(점 둘, 등록 가능), example.pvt.k12.wy.us(점 넷, 역시 등록 가능)가 제각각 다른 답을 내놓는다. 프로토콜엔 아무 규칙도, 레코드 안 플래그도, DNS 어디에도 ‘레지스트리가 관리하는 곳’과 ‘사람이 소유하는 곳’ 사이 경계를 표시하는 비트가 없다.
그런데 그 경계는 웹에서 가장 보안에 민감한 사실 중 하나다. 브라우저는 쿠키가 공유돼도 되는지 판단할 때마다 그게 필요하다. 인증 기관은 누군가 와일드카드를 요청할 때마다 그게 필요하다. URL 바는 호스트명의 어느 부분을 굵게 보여 줄지 정할 때 그게 필요하다. 셋 다 같은 질문을 던진다 — 이 이름에서 공개된 부분은 어디서 끝나는가? — 그리고 DNS는 대답을 거부한다.
그래서 웹은 그 답을 손으로 만들었다. 텍스트 파일이다.
쿠키 몬스터
이 문제가 애초에 풀린 이유는 이름 하나는 근사한 버그다: 슈퍼쿠키.
쿠키는 누가 되읽어도 되는지 말해 주는 Domain 속성을 지닌다. Domain=example.com으로 설정하면 example.com 아래 모든 호스트가 쿠키를 받는다. 합리적이다. 그런데 그 속성을 단속하던 초기 규칙 — 옛 쿠키 명세의 규칙 — 은 헛웃음이 날 만큼 조잡한 휴리스틱이었다: 한 사이트가 최상위 도메인 전체에 쿠키를 설정하지 못하게 하려고, 브라우저는 도메인에 점이 최소 몇 개는 있어야 한다고 요구했다. com엔 점이 없으니 Domain=com은 거부됐다. 좋다.
문제는 co.uk엔 점이 있다는 것. 점 세기 규칙 아래에서 co.uk는 정확히 평범한 등록 가능 도메인처럼 보였고, 그래서 evil.co.uk에 앉은 사이트가 .co.uk로 스코프된 쿠키를 심을 수 있었다 — 그러면 co.uk 아래 모든 은행·상점·정부 서비스가 그 쿠키를 도로 넘겨준다. 한 사이트가 한 나라어치의 서로 무관한 웹사이트 전역에서 읽히는 상태를 심을 수 있었던 것이다. 점이 거짓말을 한 이유는, 등록 가능 부분 앞의 점 개수가 .com과 .co.uk와 .pvt.k12.wy.us에서 다 다르고, 그걸 맞히는 공식이 존재하지 않기 때문이다.
2007년, Mozilla는 공식을 찾기를 포기하고 그냥 답을 적어 버렸다. 파일 이름은 effective_tld_names.dat였다 — effective TLD, 즉 점이 박혀 있어도 등록 목적상 최상위 도메인처럼 행동하는 접미사들. 이건 Public Suffix List로 자라났고, 지금은 누구나 읽고 누구나 변경을 제안할 수 있는 평평한 텍스트 파일로 GitHub에 사는 커뮤니티 프로젝트다.
점 세기 시대를 2011년에 대체한 쿠키 명세 RFC 6265는 이를 반쯤 공식화했다. 브라우저를 향한 그 지시는 문제를 외주 준다는 사실에 유난히 솔직하다: user agent는 가능하면 최신 public suffix list, 예컨대 Mozilla 프로젝트가 publicsuffix.org에서 관리하는 것을 사용해야 한다(SHOULD). 명세는 막으려는 공격까지 이름 붙인다 — attacker.com이 Domain=com 쿠키로 example.com의 무결성을 교란하는 것을 막기. 쿠키가 격리를 유지하는 방법의 표준이 URL 하나를 가리키며 사실상 이렇게 말한다, 저기 가서 찾아봐.
Public suffix 더하기 하나
용어를 정리해 둘 가치가 있다. 한 번 보이면 어디에나 있으니까. public suffix는 대중이 등록할 수 있는 이름이다: com, co.uk, github.io. 당신이 실제로 소유하는 것은 public suffix에 레이블 하나를 더한 것 — eTLD+1, 등록 가능 도메인. mybank.co.uk에서 public suffix는 co.uk이고 +1이 mybank.co.uk를 경계로 만든다. 쿠키, same-site 검사, ‘이 두 호스트는 같은 소유자인가’ 판단이 전부 eTLD+1을 축으로 돈다. 웹이 가진 것 중 사이트의 정의에 가장 가까운 것이고, 그건 전적으로 그 리스트로 정의된다.
리스트엔 세 종류의 규칙이 있고, 세 번째에서 이 모든 게 얼마나 수작업인지 느껴진다. co.uk 같은 평범한 줄은 적힌 그대로다. *.ck 같은 와일드카드 줄은 쿡 제도의 .ck 아래 모든 레이블이 public suffix라는 뜻이다 — example.ck가 아니라 example.co.ck를 등록하라는 것. 그리고 예외, !www.ck는 www.ck를 다시 평범한 등록 가능 이름으로 도려낸다. 어느 시점에 www.ck가 실제 사이트로 해석됐고 와일드카드가 그걸 깨뜨렸을 테니까. 누군가 그걸 발견했고, 누군가 느낌표 붙은 줄을 추가해 고쳤다. 파일 전체의 질감이 그렇다: 야생에서 발견되고, 손으로 기운 엣지 케이스들.
더는 쿠키만의 이야기가 아니다
이 리스트가 쿠키만 관장했어도 이미 하중을 견디는 부품이었을 것이다. 하지만 이건 소유권이 어디서 시작되는지 알아야 하는 모든 곳으로 새어 나갔다.
인증 기관은 와일드카드 인증서를 발급하기 전에 이걸 참조한다. CA/Browser Forum의 Baseline Requirements는 단호하다: CA는 *.co.uk나 *.local을 발급해선 안 되고(MUST NOT), *.example.com은 발급해도 된다(MAY). 차이는 정확히 public-suffix 경계다 — 레지스트리 통제 접미사 오른쪽 한 레이블의 와일드카드는 보유자가 그 레지스트리 내 모든 사이트를 사칭하게 해 주니 금지된다. 그리고 Requirements는 조용한 부분을 소리 내어 말한다. 무엇이 레지스트리 통제인지 판별하는 일은 “표준화되어 있지 않으며… DNS 자체의 속성이 아니다.” 전 세계 TLS 발급 규칙을 쓰는 사람들이, 자기들이 의존하는 사실이 프로토콜에 없다는 걸 안다. 그들도 리스트에 기댄다.
그리고 private 섹션이 있다. 파일은 ICANN 부분(진짜 레지스트리 접미사)과 PRIVATE 부분으로 갈리는데, 후자엔 회사들이 자기 도메인을 넣는다: github.io, herokuapp.com, vercel.app, blogspot.com. 이유는 미묘하고 중요하다. github.io에서 alice.github.io와 bob.github.io는 서로의 쿠키를 읽거나 서로의 서브도메인을 덮는 인증서를 받아선 안 되는 두 낯선 사람이다. 리스트 항목이 없으면 브라우저는 github.io를 등록 가능 도메인으로 취급하고 두 사용자를 같은 사이트로 본다. github.io를 PRIVATE 섹션에 추가함으로써 GitHub는 지구상 모든 브라우저와 CA에 말한다: 여기 각 서브도메인을 각자의 섬으로 취급하라. 멀티테넌트 플랫폼은 텍스트 파일에 줄 하나를 얻어 격리에 옵트인한다.
텍스트 파일이 이걸 떠받치고 있다
리스트에 공정하고 싶다: 이건 작동한다. 거의 20년 동안 한 부류의 교차 사이트 공격을 조용히 막아 왔고, 대안 — 공식 — 은 존재할 수 없음이 증명돼 있다. 레지스트리 정책은 추출할 패턴이 없는 인간의 산물이니까. 그걸 적어 둔 것은 옳은 판단이었다.
하지만 우리가 그 위에 무엇을 지었는지 보라. 웹 전역에서 쿠키·인증서 발급·유사-동일출처 판단을 통제하는 보안 경계가, 자원봉사자들이 관리하고 pull request로 바뀌며 브라우저·라이브러리 릴리스 안에 구워져 사용자에게 배송되는 손수 큐레이션한 텍스트 파일이다. 마지막 대목이 날카로운 모서리다. 새 접미사가 추가될 때 — 새 TLD, 혹은 드디어 서브도메인 격리를 원하는 SaaS 플랫폼 — 변경은 병합되는 순간 발효되지 않는다. 몇 달 뒤, 소프트웨어 업데이트 안에 실려 나가고 오래된 캐시본이 만료되면서 발효된다. 서브도메인 격리를 요청하는 새 플랫폼은 전 세계에 업데이트를 요청하는 것이고, 그런 다음 기다린다.
리스트를 아예 안 쓰는 곳에선 더 나빠진다. 점 세기 버그는 사실 죽은 적이 없다. 그냥 뒤 두 레이블을 잘라 ‘도메인을 추출’하는 수천 개의 순진한 URL 파서 속으로 옮겨 갔을 뿐이다. 그 코드는 co.uk에서 틀리고, .ck에서 완전히 틀리며, 작성자들은 대개 Public Suffix List가 존재한다는 것도 모른다. 자기만의 도메인 로직을 굴리는 모든 애널리틱스 파이프라인, 쿠키 라이브러리, 피싱 필터가 오직 그 리스트만 실제로 아는 경계를 조용히 재유도하고 있다.
여기 깊은 진실은, DNS가 이름의 트리는 주지만 그 어느 부분을 누가 관리하는지는 말해 주지 않는다는 것이다. 소유권 경계는 프로토콜이 그냥 인코딩하지 않는, 실재하고 하중을 견디며 보안에 민감한 속성이다 — 그리고 웹은 프로토콜을 고치는 대신 그 구멍을 대역 밖에서, 파일 하나로 기웠다. 브라우저가 쿠키를 통과시킬 때마다, CA가 와일드카드에 서명할 때마다, URL 바가 세 레이블 대신 두 레이블을 굵게 칠할 때마다, 그건 소수의 사람이 손으로 관리하는 리스트를 참조하고 있다.
인터넷은 아무도 편히 말하지 못할 만큼 이런 것들 위에서 돌아간다. 이건 그중 하중을 견디는 것 하나다.