노트북 시계를 2년 앞으로 돌리고 아무 웹사이트나 열어보라. 안 열린다. “경고와 함께 열림”이 아니라, 브라우저가 아예 거부한다 — NET::ERR_CERT_DATE_INVALID. 거짓말하는 시계 기준으로 이 기기의 모든 인증서가 이제 만료됐기 때문이다. 반대로 2010년으로 돌리면 거울상이 나온다: 아직 몇 년은 지나야 유효해질 인증서들. HTTPS의 신뢰 장치 전체가 조용히 ‘기기가 지금 몇 시인지 안다’고 가정하고 있고, 그 가정이 깨지는 순간 어떤 암호학도 당신을 못 구한다.
이 부분이 충분히 언급되지 않는다고 생각한다. 우리는 인증서 검증을 수학 문제처럼 이야기한다 — 서명, 체인, 신뢰 루트. 그런데 그 한복판에는 인증서 안의 두 타임스탬프 notBefore와 notAfter를 로컬 시계와 비교하는 과정이 박혀 있다. 암호학이 아니다. 그냥 if (now > notAfter) reject다. 그리고 그 now는 어딘가에서 온다.
시계에 기대고 있는 것들
한번 보기 시작하면, 시계가 하중을 받치고 있는 곳이 불편할 만큼 많다.
TLS 인증서엔 유효 구간이 있고 그 구간은 로컬 시각으로 강제된다. HSTS는 만료가 있는 약속이다 — max-age가 시계를 기준으로 카운트다운하고, 자기가 먼 미래라고 믿는 기기는 그 약속이 끝났다고 판단한다. DNSSEC 서명은 더하다: 모든 RRSIG 레코드가 명시적 inception·expiration 시각을 담고 있어서, 시계가 그 구간 밖에 있는 검증기는 멀쩡한 서명을 아직-유효하지-않음 또는 만료로 거부한다. Kerberos 티켓은 시각에 묶여 있고 시계가 5분 넘게 어긋나면 유명하게 나가떨어진다. TOTP — 인증 앱의 여섯 자리 코드 — 는 말 그대로 현재 30초 타임스텝의 해시다. 전체 방식이 “우리 둘 다 지금 몇 시인지 안다”에 기반한다. JWT는 타임스탬프로 만료된다. 장애 대응 때 의지하는 시스템 간 로그 상관관계도, 모든 장비에서 타임스탬프가 같은 의미라고 가정한다.
추상을 벗겨내면, 우리가 “보안”이라 부르는 것의 상당 부분이 서명으로 치장한 ‘현재 시각에 대한 주장’으로 드러난다. 그러면 자명한 질문이 생긴다: 컴퓨터는 시각을 어디서 얻고, 우리는 그 경로를 얼마나 믿는가?
아무나-믿는 프로토콜
사실상 인터넷의 모든 기기에서 그 답은 NTP — Network Time Protocol, 최신 규격은 RFC 5905의 NTPv4다. 아직도 매일 쓰이는 가장 오래된 프로토콜 중 하나이고, 아름답게 작동한다: 서버 계층 구조, 꼭대기의 stratum 0(실제 원자시계·GPS 수신기), 각 층이 위층에서 동기화, 네트워크 지터를 걸러내고 참 시각을 밀리초 이내로 잡아내는 영리한 알고리즘.
여기 아무도 말 안 하는 게 있다. 압도적으로 흔한 기본 설정에서, NTP는 완전히 미인증이다. 당신 기기는 시각을 요청하고, 오는 답을 믿는다. 누가 보냈는지 검증하지 않는다. 확인할 서명도, 확인할 신원도 없다. 먼저 도착한 패킷이 “3시 47분 12초야”라고 하면, 그게 믿긴다. 인증서 검증·HSTS·DNSSEC 밑에 깔린 프로토콜치고는, 서명 없는 UDP 패킷 하나에 두는 신뢰치고 놀라운 양이다.
NTP 생애 대부분 동안 그냥 그런 식이었고, 대체로 문제가 안 됐다. 대체로 아무도 회선에서 시각을 두고 거짓말하지 않았으니까. 그 문장에서 “대체로”가 아주 많은 일을 하고 있다.
남의 시계를 일부러 옮기기
2015년, 보스턴 대학 연구진 — Malhotra 외 — 이 근사하게 직설적인 제목의 논문을 냈다: Attacking the Network Time Protocol. 표적이 지금 몇 시라고 믿는지를 통제할 수 있으면 뭘 할 수 있는지를 정확히 펼쳐 보였다. 그 목록은 위에 나온 모든 것의 순회다. 피해자 시계를 앞으로 밀면 멀쩡한 인증서를 만료시키거나, HSTS max-age를 넘겨 다운그레이드 보호를 무력화할 수 있다. 뒤로 밀면 죽었어야 할 것을 되살릴 수 있다 — 폐기·은퇴된 인증서가, 아직 작년이라 믿는 기기에겐 “유효”해진다. 충분히 굴리면 DNSSEC 서명이 유효 구간 밖으로 나가 검증이 붕괴하고, 엄격한 리졸버에겐 이름이 아예 안 풀린다는 뜻이 된다.
그리고 이걸 하는 데 어떤 암호학도 깰 필요가 없었다. 그 모든 검사가 조용히 의존하는 단 하나의 입력을 바꿨을 뿐이다. 경로 상의 중간자는 NTP 요청에 그냥 거짓으로 답하면 된다. 논문은 off-path 기법도 보여줬다 — IPv4 단편화 악용, NTP 클라이언트가 특정 서버 응답을 처리하는 방식 악용 — 그래서 엄밀히 중간에 앉아 있을 필요조차 없었다. 시계가 물렁한 아랫배였고, 내내 미인증인 채로 거기 있었다.
왜 40년이나 그대로였을까? 시도가 없어서가 아니다. NTP엔 Autokey(RFC 5906)라는 인증 방식이 있었는데, 좋게 말해도 별로였다 — 설계에 심각한 암호학적 약점이 있어서 사실상 아무도 실전 배치하지 않았다. 다른 선택지인 대칭키 인증은 작동은 하지만 당신과 시각 서버가 미리 비밀키를 공유해야 한다. 한 조직의 몇 대엔 괜찮지만, 수백만 낯선 이에게 서비스하는 공개 시각 서버엔 완전히 불가능하다. 그래서 NTP를 지켰을 메커니즘은 작동을 안 하거나 확장이 안 됐고, 인터넷은 패킷을 믿는 쪽으로 기본값을 잡았다.
같은 프로토콜, 밖으로 겨누면, 무기
NTP 보안 이야기엔 후반부가 있고, 우울하게도 좀 웃기다. 아무것도 인증하지 않던 그 프로토콜이, 지난 10년 최대급 DDoS 증폭기 중 하나이기도 했다.
옛 NTP 서버는 monlist라는 진단 명령을 지원했는데, 서버와 최근 통신한 주소 600개를 돌려줬다. 작은 요청, 거대한 응답 — 게다가 NTP는 UDP라 출발지 주소를 위조해 그 거대한 응답을 피해자에게 떨굴 수 있다. 그게 CVE-2013-5211이고, 2014년 2월 이걸로 약 400 Gbps에 이르는 반사 공격이 벌어졌다. 당시 최대급 중 하나였고, 잘못 설정된 무고한 시각 서버 수천 대로 만들어졌다. 증폭 배율 100배 이상. 인터넷 시계를 맞추던 프로토콜이 잠시 소방호스로 더 유명해졌다.
monlist는 비활성화됐고 증폭 문제는 잦아들었지만, 유용한 상기점이다: 시각 인프라도 인프라이고, 그에 딸린 모든 공격 표면을 갖는다. 그런데 오랫동안 거의 아무도 그렇게 대하지 않았다.
해법은 존재한다. 얼마나 쓰이는지 맞혀보라.
이제 진짜 답이 있다. Network Time Security — NTS, RFC 8915, 2020년 9월 발행 — 이 드디어 자명한 걸 한다: TLS 위에서 키를 부트스트랩하고(그래서 신뢰를 세울 인증된 채널을 얻고), 그 키로 실제 NTP 교환을 AEAD로 인증하되, 전부 NTP 확장 필드에 실어 기존 프로토콜과 호환을 유지한다. 이제 당신 기기는 시각이 의도한 그 서버에서 왔고 도중에 변조되지 않았음을 확인할 수 있다. Cloudflare는 time.cloudflare.com에 공개 NTS 서버를 운영하고, Netnod 등도 운영한다. 클라이언트도 있다 — chrony와 ntpsec이 지원한다.
채택률이 반올림 오차라는 데 놀랄 것이다. 대부분 운영체제는 여전히 평문 미인증 NTP를 기본으로 배포한다. 대부분 사람은 NTS를 들어본 적도 없다. 안전한 선택지가 몇 년째 표준화돼 무료로 있는데, 지금 이걸 읽는 기기의 시계는 거의 확실히 여전히 ‘먼저 답한 놈’을 믿는 프로토콜로 맞춰지고 있다. 이게 DNSSEC 이야기, MTA-STS 이야기, OCSP 이야기처럼 들린다면 — 진짜 해법이 안 쓰인 채 놓여 있고 안전하지 않은 기본값이 굴러가는 — 같은 이야기이기 때문이다. 누군가 잘 돌아가는 기본값을 바꾸러 가야 하는 보안 개선은 대체로 일어나지 않는다.
아무도 안 적는 의존성
이게 진짜로 거슬리는 지점이다. 우리는 지난 10년간 모든 걸 시계에 더 세게 기대게 만들었다. 인증서 수명은 계속 짧아진다 — 398일, 그다음 200일, 47일을 향해 — 바로 유효 구간을 촘촘히 해서 유출된 키가 더 빨리 무의미해지게 하려고. HSTS는 더 엄격해졌다. DNSSEC 서명엔 inception·expiration이 박혀 있다. 이 개선들 하나하나가 기기가 지금 몇 시인지 안다고 가정한다. 우리는 모든 창(window)을 조여놓고, 정작 그 창을 정하는 것은 서명 없는 패킷을 믿게 놔뒀다.
공격자만 문제도 아니다. 시각은 아무도 노리지 않아도 취약하다. 2012년 6월 말 leap second가 삽입됐을 때, Linux 커널 timekeeping 코드의 버그 — 그 1초가 일어났다고 high-resolution timer 서브시스템에 안 알린 것 — 가 CPU를 폭주시켰고, Reddit·Mozilla·LinkedIn·Foursquare·Yelp와 여러 항공사 시스템의 서버가 동시에 쓰러졌다. 공격받은 게 아니다. 그냥 시간이 살짝 이례적인 짓을 했다는 걸 정확히 통보받고 감당을 못 한 것이다. Google 등이 “leap smear”를 발명해 여분의 1초를 하루에 걸쳐 펴 발라 아무것도 :60을 안 보게 한 이유다. 세계 도량형학자들이 2022년 leap second를 2035년까지 폐지하기로 표결한 이유다 — 수십 년 묵은 장애 원인의 가장 안전한 해법이, 그 짓을 그만두는 것이었다.
위협 모델에 올려라. 시계 공격이 당신에게 벌어질 가장 유력한 일이어서가 아니라 — 아니다 — 시계가 의존성 밑의 의존성이기 때문이다. 아키텍처 다이어그램에 안 나오는, 네트워크 너머 누군가 제공하는 서비스가 아니라 물리 법칙처럼 느껴지는 그것. 서비스다. 40년간 낯선 이들을 믿어왔다. 그리고 당신 보안의 섬뜩할 만큼 큰 몫이, 사실은 지금 몇 시인지에 대한 잘 서명된 진술일 뿐이다.