Flame은 마이크로소프트 서명을 위조했다

2012년, 한 첩보 툴킷이 자신을 '서명된 윈도우 업데이트'로 위장해 감염 대상 PC에 설치됐다. 서명하려고 그들은 학계가 아직 발표하지 않은 MD5 충돌로 마이크로소프트 인증서를 위조했다 — 줄줄이 늘어선 제도적 실수의 마지막 고리로.

2012년 5월, 중동에서 발견된 첩보 툴킷을 뜯어보던 연구자들이 멈칫하게 만드는 것을 만났다. 카스퍼스키는 Flame, 부다페스트의 CrySyS 연구소는 sKyWIper, 이란 CERT는 Flamer라 부른 이 악성코드는, 같은 네트워크의 다른 머신들에 윈도우 업데이트를 건네줌으로써 스스로 퍼질 수 있었다. 진짜처럼 보이는 업데이트를. 마이크로소프트가 서명한.

바로 그 마지막 부분이 핵심이다. 업데이트인 척하는 프로그램은 누구나 짤 수 있다. 관건은 윈도우가 그걸 받아들이게 만드는 것이고, 윈도우가 업데이트를 받아들이는 이유는 마이크로소프트 루트로 이어지는 인증서가 서명했기 때문이다. 이걸 위조하려면 마이크로소프트의 서명 키를 훔치거나 — 아무도 안 훔쳤다 — 마이크로소프트가 서명하지 않았는데도 윈도우가 서명했다고 믿는 인증서를 만들어야 한다. Flame은 후자를 했다. 마이크로소프트가 신뢰하는 코드 서명 인증서를 위조한 것이다. 그리고 그걸 해낸 암호기법은, 그걸 판단하기에 가장 적합한 사람의 말에 따르면, 공개 연구계가 아직 발표하지 않은 기법이었다.

인증서는 사슬이고, 모든 고리가 각각은 그럴듯했다

가짜 업데이트가 실제로 어떻게 작동했는지부터 보자. 암호기법은 첫 단계가 아니라 마지막 단계다.

Flame은 세 개의 모듈을 실어, 함께 작동하며 감염된 PC를 이웃들을 위한 악성 업데이트 서버로 바꿨다. 하나는 로컬 네트워크에서 머신들이 프록시를 찾을 때 뿌리는 브로드캐스트 질의 — WPAD 요청 — 를 감시하다 자기가 응답했고, 그래서 근처 컴퓨터들은 자기 트래픽을, 윈도우 업데이트 확인을 포함해, 감염된 상자를 거쳐 흘리기 시작했다. 다른 하나는 마이크로소프트 업데이트 엔드포인트를 사칭하는 HTTP 서버를 돌렸다. 세 번째는 피해자가 받을 페이로드를 조립했다 — 약 28KB의 작은 서명 실행 파일로, 정상 업데이트인 양 굴며 경고창 하나 없이 실행됐다.

이 모든 게 유효한 서명 없이는 안 된다. 그리고 여기서 “각 조각은 개별적으로 합리적이었다”는 사슬이 시작된다.

마이크로소프트는 터미널 서버 라이선싱을 위한 서비스를 운영했다 — 원격 데스크톱 배포가 클라이언트 액세스 라이선스를 활성화하게 해주는 배관 설비다. 그 서비스는 인증서를 발급했고, 활성화를 매끄럽게 하려고 발급 인증서를 마이크로소프트 루트 권한(Root Authority)까지 죽 이어지게 했다. 여기까지는 평범하다 — 회사가 자기 루트로 자기 운영 인증서에 서명하는 것. 그 라이선싱 인증서 등록에는 마이크로소프트의 진짜 코드 서명 인프라에 대한 내부 접근이 필요 없었다 — 이 역시 합리적이다. 라이선싱은 소프트웨어 배포와 무관하니까.

문제는 “마이크로소프트 루트로 이어진다”가 바로 윈도우가 서명된 코드를 신뢰할지 판단할 때 확인하는 그것이라는 점이다. 구버전 윈도우에서는 그 라이선싱 서비스의 인증서가 코드 서명에 인정됐다. 사슬 어디에도 “이건 라이선싱 전용이지 프로그램 서명용이 아니다”라고 적혀 있지 않았기 때문이다. 원격 데스크톱 좌석 수를 세려고 발급된 인증서가 실행 파일을 보증할 수 있었다. 아무도 그 기능을 설계하지 않았다. 각각은 멀쩡해 보이는 두 시스템의 교차점에서 굴러떨어진 것이다 — 이런 이야기들의 반복되는 형태이자, 눈에 익혀둘 가치가 있는 형태다.

유일한 장애물, 그리고 그것을 치운 충돌

라이선싱 인증서에는 구멍을 막았어야 할 함정이 있었다. 각 인증서에는 추가 필드가 하나 있었다 — 마이크로소프트가 내부적으로 쓰던 확장으로, *critical(치명적)*로 표시돼 있었다. X.509에서 “critical”은 이런 뜻이다. 이 필드를 이해 못 하겠거든, 인증서를 거부하라. 윈도우 비스타와 그 이후는 옳게 행동해, 그 인식 불가한 critical 확장이 붙은 인증서를 코드 서명에 받아들이길 거부했다. 그래서 최신 머신에서는 발급된 그대로의 라이선싱 인증서가 공격자에게 무용지물이었다.

당연한 수는 더 깔끔한 인증서를 만드는 것이다 — 문제의 확장이 없고, 코드 서명에 맞는 유효기간을 가진. 하지만 인증서를 그냥 편집할 순 없다. 인증서가 신뢰받는 유일한 이유는 발급자가 그 내용의 해시에 서명했기 때문이고, 한 바이트만 바꿔도 해시가 바뀌어 서명이 더는 맞지 않는다. CA가 여전히 서명한 것처럼 보이는 다른 인증서를 만들려면, 같은 해시를 갖는 두 문서가 필요하다 — 라이선싱 서비스가 실제로 서명하는 무해해 보이는 것, 그리고 당신이 챙기는 무기화된 것. 그게 해시 충돌이고, CA의 서명이 충돌시킬 만큼 약한 해시 위에 있을 때만 도움이 된다.

약했다. 라이선싱 서비스는 MD5로 서명했다.

2012년에 MD5는 놀랄 일이 아니었다. 연구자들은 2004년에 MD5 충돌을 만들어냈고, 2008년에는 CWI 암스테르담의 Marc Stevens를 포함한 팀이 MD5 chosen-prefix 충돌로 브라우저가 신뢰하는 가짜 인증기관을 발급해냈다 — 바로 이 부류의 공격이 실재한다는, 공개되고 널리 보도된 시연이었다. Chosen-prefix 충돌은 위험한 종류다. 공격자가 서로 다른 의미 있는 두 시작 — 여기서는 서로 다른 두 인증서 — 을 고정하면, 수학이 각각에 붙일 채움 바이트를 찾아 두 해시를 같은 값에 떨어뜨린다. 쓰레기 파일 두 개가 아니라, 실제의 구조화된 것을 위조하는 형태의 공격이다.

그러니까 이론도 4년 됐고 증거도 4년 됐는데, 마이크로소프트는 여전히 자기 루트로 이어지는 운영 인증서를 MD5로 서명하고 있었다. 장전된 총이 2008년부터 탁자 위에 놓여 있었던 것이다.

아직도 불편해야 할 부분

Stevens가 Flame이 실은 인증서를 살펴보니, 그것은 자신의 2008년 기법으로 만든 게 아니었다. 충돌은 chosen-prefix 공격의 흔적을 지녔지만, 공개 문헌에 등장한 적 없는 변종이었다. 그의 평가는 단호했다. 그걸 설계하려면 세계 최정상급 암호분석이 필요하다고. Flame을 만든 자가 누구든, 발표된 연구보다 앞서 있었다.

이걸 곱씹어보라. 깨진 암호를 두고 우리가 흔히 하던 마음 편한 추론을 뒤집기 때문이다. 우리가 하는 이야기 — SHA-1 충돌에 그토록 깔끔하게 들어맞던 그것 — 는 이렇다. 어떤 기법이 몇 년간 “이론적으로 깨져” 있다가, 진짜 위험은 누군가 내려받을 수 있는 개념증명(PoC)을 올리는 날 도착한다. 그 모델에선 위험이 다가오는 걸 지켜볼 수 있다. 논문을 가리킬 수 있다. Flame은 그 모델을 깬다. 가장 유능한 공격자는 PoC를 기다리지 않고, 발표된 공격에 스스로를 가두지도 않는다. 그들은 공개된 분야 전체보다 한 기법 앞서 있을 수 있고, 당신은 그들이 이미 배포한 것을 역공학할 때에야 알게 된다. “아직 공개된 공격이 없다”는 “공격이 없다”와 같지 않고, 국가급 예산 앞에서 그 둘은 몇 년씩 벌어질 수 있다.

충돌을 실전 가능하게 만든 제도적 자충수가 하나 더 있는데, 평범한 것이다. Chosen-prefix 충돌은 접두(prefix)를 미리 알아야 한다 — CA가 서명할 인증서의 정확한 바이트를 확정한 뒤에 맞물릴 채움을 계산해야 한다. 즉 라이선싱 서버가 그 인증서에 찍을 일련번호와 정확한 유효기간 타임스탬프를 좁은 창 안에서 예측해야 한다. 그 바이트들이 해싱되는 대상의 일부이기 때문이다. 라이선싱 서비스는 그 예측을 가능하게 만들었다. 일련번호는 순차적이었고 타임스탬프는 시계를 따랐다. 예측 가능한 발급은, 원래라면 예측 불가능한 접두를 알아맞혀야 할 충돌을, 해답이 있는 공학 문제로 바꿔놓았다. 일련번호를 무작위화하면 — 바로 이런 공격들 때문에 지금은 표준 CA 관행이다 — 이 접근 전체가 극적으로 어려워진다.

실제로 무엇이 고쳐졌나

마이크로소프트는 기법을 이해하자 빠르게 움직였다. 2012년 6월 3일, Security Advisory 2718704를 발표해 터미널 서버 라이선싱 계층 아래의 중간 인증기관들을 폐기했고, 그래서 그것들을 거쳐 이어지던 것들이 더는 신뢰받지 않게 됐다. 이어 라이선싱 인증서를 코드 서명 경로에서 완전히 빼내고, 윈도우 업데이트가 설치물을 인증하는 방식을 강화해, 소프트웨어 서명용으로 의도된 적 없는 인증서가 더는 소프트웨어 서명으로 휘어질 수 없게 했다. 특정 구멍 — 라이선싱 인증서를 코드 서명 인증서로 쓸 수 있던 것 — 은 막혔다.

하지만 개별 수정 중 어느 것도 MD5에 관한 게 아니었다는 점을 보라. 그게 단서다. 충돌은 마지막 고리였다. 그 앞의 고리들이야말로 표적을 공격할 가치가 있게 만든 것이었다. 루트로 이어진 라이선싱 PKI, 의도된 적 없는 코드를 서명할 수 있던 인증서, 그리고 공격자에게 필요한 접두를 건네준 예측 가능한 발급. 하나하나가 국소적으로는 합리적인 결정이었다. 합쳐지자 “원격 데스크톱 라이선스를 활성화한다”에서 “악성코드를 마이크로소프트로 서명한다”로 가는 길이 됐다.

교훈은 “MD5가 깨졌다”가 아니다

MD5가 깨진 건 다 알았다. 몇 년째 공개적으로 깨져 있었고, 이를 증명하는 헤드라인급 인증서 위조까지 있었는데도, 2012년 마이크로소프트 운영 시스템의 서명 하나를 여전히 떠받치고 있었다. 교훈은 이것이다. 운영에 남겨둔 깨진 원시함수(primitive)는 다음 기능 뒤로 미뤄도 되는 낮은 우선순위 티켓이 아니다. 그것은 당신의 인프라에 앉아, 예산과 접두를 가진 누군가가 쓰기를 기다리는 살아 있는 능력이다 — 그리고 그 누군가는 당신이 읽는 논문보다 수학을 더 잘할 수도 있다.

두 번째 교훈은 사슬에 관한 것이다. Flame은 강한 시스템을 이기지 않았다. 개별적으로 분별 있는 대여섯 개의 선택으로 짜인 길을 걸었을 뿐이고, 부분들의 보안은 전체의 보안으로 합산되지 않았다. 인증서는 문자 그대로 신뢰의 사슬이며, 흥미로운 실패는 거의 언제나 단일 고리에 있지 않다. 그것은 두 시스템이 만나는 곳, 그리고 서로가 상대편이 밧줄을 쥐고 있으리라 가정하는 자리에 있다.

토론 참여

← 블로그로 돌아가기