ROBOT: 19년 만에 돌아온 공격

1998년 블라이헨바허가 TLS의 RSA를 깼다. 그 '수정'은 취약한 우회책이었고, 2017년 상위 100대 사이트의 3분의 1에서 다시 깨졌다.

2017년 말, 세 연구자가 페이스북의 개인키로 메시지에 서명했습니다. 훔친 게 아닙니다. 훔칠 필요도 없었습니다. 20년 전에 고쳐졌다고 모두가 믿던 1998년의 버그를 facebook.com의 TLS 서버에 겨눠, 서버가 낯선 사람을 위해 절대 해서는 안 될 서명 연산을 하게 만들었습니다. 그다음 페이팔급 호스트들에도 같은 짓을 하고, 논문을 냈습니다.

이 버그는 처음엔 블라이헨바허 공격이라 불렸습니다. 두 번째엔 더 나은 이름을 얻었죠 — ROBOT, Return Of Bleichenbacher’s Oracle Threat(블라이헨바허 오라클 위협의 귀환). 약자에 담긴 농담이 이야기의 전부입니다. 새 결함이 아니었습니다. 모두가 잠갔다고 믿은 문으로 같은 옷을 입고 다시 걸어 들어온, 같은 결함이었습니다.

1998년 블라이헨바허가 발견한 것

다니엘 블라이헨바허의 원래 공격은 TLS가 키 교환에 쓰던 RSA를, 정확히는 PKCS#1 v1.5 패딩 방식을 노렸습니다. 클라이언트가 옛 방식의 RSA 세션을 열려면 서버의 공개키로 비밀을 암호화해 보냈고, 서버는 그걸 복호화해 패딩이 올바른지 확인했습니다.

그 확인이 실수였습니다. 수학이 아니라 반응이 문제였죠. 패딩이 맞을 때와 틀릴 때 조금이라도 다르게 행동하는 서버는 추측 한 번마다 1비트를 흘리고 있었습니다 — 패딩이 유효한가? 그 차이를 볼 수 있는 공격자는 가로챈 암호문을 정교하게 변형한 수천 개를 보내, 어느 것을 서버가 “받아들이는지” 보고, 그 예/아니오 하나하나로 암호화를 한 단계씩 벗겨낼 수 있었습니다. 이것이 패딩 오라클입니다. 서버가 거부하고 있다고 믿는 질문에 자기도 모르게 답하는 것이죠.

원래 공격은 세션 키 하나를 복구하는 데 대략 100만 번의 질의가 필요해, ‘백만 메시지 공격’이라 불리기도 합니다. 서버에 요청 100만 번은 별난 일이 아닙니다. 오후 한나절이면 됩니다.

수정이 아니었던 수정

곱씹어 볼 대목이 여기입니다. 블라이헨바허에 대한 대응은 깨진 패딩을 그만 쓰는 것이 아니었습니다. PKCS#1 v1.5를 쓰는 RSA 키 교환은 그 뒤로도 20년을 TLS에 남았습니다. 대신 표준은 우회책을 처방했습니다 — 패딩이 유효하든 아니든 서버가 똑같이 행동하게 만들라는 것입니다. 복호화가 실패하면 에러를 보내지 말고, 무작위 비밀을 만들어 아무 일 없던 듯 계속 진행해, 핸드셰이크가 나중에 다른 수많은 실패와 똑같아 보이는 방식으로 실패하게 합니다.

그걸 완벽하게 구현하면 오라클은 침묵합니다. 모든 무게를 지는 단어가 완벽하게입니다. 우회책은 취약점을 제거하지 않습니다. 두 코드 경로에서 서버가 구별 불가능하리라는 약속 뒤에 숨길 뿐입니다. 관찰 가능한 차이가 하나라도 있으면 오라클이 다시 열립니다 — 다른 TLS 경고 번호, 타임아웃 대신 리셋되는 연결, 몇 밀리초 먼저 도착하는 에러, 잘못된 메시지에 대한 미묘하게 다른 응답. 공격자는 어느 신호가 새는지 상관하지 않습니다. 하나면 충분합니다.

2017년: 실은 떠난 적이 없었다

한노 뵈크, 유라이 소모로프스키, 크레이그 영은 아무도 대규모로 해보지 않았던 일을 했습니다 — 직접 확인한 것입니다. 1998년의 공격을 현대 구현에 맞게 갱신해 실제 인터넷에 겨눴습니다.

통했습니다. 상위 100대 도메인의 약 3분의 1이 취약했고, 문제의 코드는 인프라 벤더 명단을 그대로 옮긴 듯했습니다 — F5, 시트릭스, 라드웨어, 시스코, 그 외 웹의 거대한 몫 앞단에 놓인 로드밸런서와 TLS 종단 장비들. 방치된 소프트웨어를 돌리는 무명 서버가 아니라, 온라인 최대 사이트들을 지키던 장비였습니다.

이들 모두 원칙적으로는 그 우회책을 갖고 있었습니다. 그리고 모두, 수정을 처음 설계한 이들이 예상하지 못한 어딘가에서 구별 가능한 신호를 흘렸습니다. 19년 동안 모두가 문제는 해결됐다고 믿었고, 문제는 내내 거기 앉아 있었습니다 — ‘해결책’이 너무 많은 구현이 조용히 떨어져 나간 외줄타기였기 때문입니다.

교훈은 RSA가 아니라 우회책에 관한 것

이걸 “RSA는 나쁘다”로 분류하고 넘어가기 쉽습니다. 틀린 교훈입니다. 옳은 교훈은 결함을 고치는 것과 덮는 것의 차이에 관한 것입니다.

“위험한 걸 계속하되 조심하라”는 패치는 수정이 아닙니다. 이자가 붙는 빚입니다. 새 구현 하나, 리팩터링 하나, 에러 경로를 바꾸는 성능 개선 하나가 전부, 우회책이 숨기던 바로 그 버그를 되살릴 새 기회입니다. 버그는 공격자가 재발견할 필요도 없습니다. 에러 반환을 세 줄 위로 옮긴 선의의 개발자가 재구성하면 됩니다. 그리고 오래전 끝난 문제라고 다들 믿으니, 아무도 보고 있지 않습니다.

우리는 이걸 계속 어렵게 배웁니다. regreSSHion은 훗날의 변경이 조용히 되살린 2006년 OpenSSH 버그였습니다. ROBOT은, 그 수정이 수십 개 독립 코드베이스에 걸쳐 누구도 지킬 수 없는 행동 약속이었기에 살아남은 1998년의 공격이었습니다. 패턴은 똑같습니다 — 취약한 완화책으로 연명하는 위험한 기본요소가, 그 조심스러운 부분을 무질서가 풀어버리기를 기다리는 것.

ROBOT을 실제로 죽인 것은 더 나은 우회책이 아니었습니다. 제거였습니다. TLS 1.3은 RSA 키 교환을 통째로 걷어냈습니다 — 공격할 정적 RSA 핸드셰이크가 남아 있지 않고, 순방향 비밀성을 갖는 임시 키 합의(ECDHE 계열)만 남습니다. 이 방식은 서버에게 클라이언트가 고른 암호문을 복호화하라고 요구하지 않으므로, 흘릴 오라클 자체가 없습니다. 연구자들의 권고는 무뚝뚝하고 옳았습니다 — RSA 암호화 키 교환을 폐기하고 PKCS#1 v1.5를 더 이상 끌고 가지 말 것.

때로 버그를 고치는 유일한 방법은 그것이 사는 집을 지우는 것입니다. 우회책은 시간을 벌어줍니다. 안전을 사주지는 않고, 계량기는 늘 돌아가고 있습니다.

토론 참여

← 블로그로 돌아가기