blisstoner's profile image

blisstoner

August 21, 2021 11:00

TLS 1.3 프로토콜

Cryptography

1. Introduction

8월 16일 오후 3시부터 24시간동안 삼성에서 열린 SCTFDecryptTLS라는 이름의 TLS 관련 문제가 출제되었습니다.

해당 문제에서는 구글 웹사이트와 통신한 TLS 패킷파일이 제공되어서 해당 파일로부터 플래그를 알아내야 했습니다. 문제에 들어있던 암호학적 취약점 자체는 그렇게 복잡하지는 않았지만 정작 프로토콜의 Specification을 보고 데이터를 추출하는데에 rkm0959님과 같이 아주 긴 시간동안 고통을 받았습니다.

그래도 그 과정에서 TLS 1.3 버전에 대한 공부를 할 수 있었어서 TLS 1.3에 대한 포스팅을 작성하고자 합니다.

2. TLS

옛날 옛적에는 웹서버와 통신을 할 때 암호화가 되어있지 않은 경우가 많았습니다. 그렇기 때문에 이를테면 특정 사이트에 로그인을 하기 위해 아이디와 비밀번호를 입력하면 해당 정보가 중간에 암호화되지 않고 그대로 서버로 전송이 되었고, 네트워크 상에서 클라이언트와 서버 중간에 위치해 데이터를 옮겨주는 ISP, 인터넷망 관리자 등은 마음만 먹으면 모든 민감한 데이터를 확인할 수 있었습니다.

이 문제를 해결하기 위해 인터넷에서 정보를 암호화해서 주고받는 프로토콜인 TLS(Transport Layer Security)가 등장했고, 현재를 기준으로 거의 대부분의 웹사이트는 TLS 통신을 지원합니다. 웹사이트에서 주소창이 https로 시작하면 TLS 통신을 진행한다는 의미입니다.

TLS가 달성하고 싶은 시나리오를 교도소에 갇혀있는 죄수로 비유를 하면 아래와 같습니다.

두 죄수 A와 B가 교도소의 서로 다른 방에 갇혀있다. A와 B는 탈옥 계획을 세우고자 한다. A와 B는 직원을 통해 서로 편지를 주고받을 수 있지만 직원은 둘 사이에서 오가는 모든 편지의 내용을 확인할 수 있다. 즉 A와 B는 편지의 내용이 직원에게 공개되는 상황에서 편지로 논의를 이어가야 한다.

이런 상황을 만족시킬 수 있는 가장 간단한 방법은 공개키 암호화 시스템을 이용하는 방법입니다. 만약 클라이언트와 서버가 상대의 공개키를 알고 있다면 상대방의 공개키로 메시지를 암호화한 후 보내면 됩니다. 설령 누군가가 암호화된 메시지를 보더라도 비밀키를 모르는 한 메시지로부터 아무런 의미있는 정보를 얻을 수 없습니다. 하지만 이 방식에는 치명적인 단점이 있는데, 공개키 암호화는 대칭키 암호화에 비해 일반적으로 연산 속도가 상당히 느립니다. 그래서 공개키 암호화만을 이용한 위와 같은 방식을 사용할 경우 너무나 비효율적입니다. 그렇기 때문에 TLS에서는 마치 공개키 암호화 시스템과 비슷한 원리인 Diffie-Hellman 키 교환을 이용해 클라이언트와 서버 사이에서 사용할 키 K를 만들고, K를 대칭키 암호화 시스템에서의 키로 사용합니다.

3. TLS 1.2 vs TLS 1.3

TLS 1.2버전 또한 Diffie-Hellman을 이용해 키교환을 진행하고 서로 공유한 비밀키를 이용해 대칭키 암호화를 하는 방식은 동일합니다. 그러면 TLS 1.3버전에서 어떤 점이 개선됐는지를 살펴보겠습니다.

Handshake 과정

먼저 Handshake에서 TLS 1.2버전과 1.3버전의 차이를 간단하게 짚어보자면, 아래의 그림과 같이 Handshake 과정에서 필요한 서로간의 통신 횟수가 TLS 1.3버전에서 줄어들었습니다.

TLS v1.2

TLS v1.3

통신 횟수가 줄어듬으로서 자연스럽게 키를 교환하기까지 필요한 시간이 줄어들었습니다.

취약한 알고리즘 제거

TLS 1.2 버전은 2008년에 릴리즈되었습니다. 그렇기 때문에 그 사이에 POODLE, DROWN 등과 같은 여러 공격들이 제시되었고, RC4, DES, 3DES, MD5, SHA-1등과 같이 2021년 기준으로는 이미 여러 취약점이 공개되어 사실상 제 기능을 할 수 없는 암호 시스템 또한 표준에 포함되어 있어서 서버에서 직접 이러한 암호 시스템으로 연결을 시도할 경우 거절을 해야해야 했습니다. 반면 TLS 1.3 버전에서는 이미 취약점이 알려진 여러 암호 시스템들을 전부 표준에서 제거했습니다.

Perfect Forward Secrecy

공개키 암호화를 생각해보면, 서버의 비밀키/공개키는 짧은 주기로 바꾸기가 힘듭니다. 만약 효율성은 포기하고서라도 서버와 공개키 암호화만을 이용해 통신을 한다고 가정해보면 서버의 비밀키/공개키를 1년 주기로 변경한다고 했을 때, 만약 감청 기관에서 특정 사람이 서버와 주고받은 암호화된 패킷을 전부 저장하고 있었는데 우연한 사고로 서버의 비밀키를 획득했다면 그 기관은 지난 1년간의 모든 패킷을 복호화할 수 있습니다.

반면 Diffie-Hellman 키 교환을 이용한 방법에서는 설령 서버의 비밀키가 어떠한 이유로 공개되더라도 Diffie-Hellman 키 교환을 올바르게 수행했다면 특정 패킷에 쓰인 비밀키를 알아낼 수 없습니다. 이러한 성질을 Perfect Forward Secrecy라고 합니다. TLS 1.2 버전에서도 Perfect Forwad Secrecy를 지원하지만 TLS 1.3 버전에서는 Perfect Forwad Secrecy를 지원하지 않는 RSA를 이용한 키 교환 방식 등을 전부 제거했기 때문에 모든 경우에 Perfect Forward Secrecy가 만족됩니다.

4. TLS 1.3 패킷 분석

실제 SCTF 2021 DecryptTLS에 쓰인 TLS 1.3 버전의 패킷을 가지고 TLS 프로토콜에 대해 이해해보도록 하겠습니다. 패킷 파일을 제가 따로 첨부하지는 않았지만 주최측에서 문제들을 빠른 시일내에 공개하겠다고 했으니 필요할 경우 직접 검색을 통해 파일을 받아 분석을 해볼 수 있습니다. 제공된 패킷 파일은 Wireshark를 이용해 분석했습니다.

일단 파일을 열어보면 서버 ip 142.251.8.105와 통신을 주고받은 것을 확인할 수 있습니다. Wireshark 프로그램이 이미 자체적으로 어느 정도는 패킷을 파싱해주기 때문에 통신에 TLS 1.3버전을 사용했고, 포트 번호가 443이라는 점에서 HTTPS 프로토콜임을 짐작할 수 있습니다. 또한 인터넷 브라우저를 이용해 142.251.8.105에 접속을 해보면 google.com로 리다이렉트되는 것을 확인 가능하니 이 통신 기록은 클라이언트가 google.com에 접속하는 과정에서 주고받은 패킷을 기록해놓은 것임을 짐작할 수 있습니다.

10번째 이후의 내용은 암호화가 되어있기 때문에 그 전의 Handshake 과정을 들여다보고 암호화 키를 복원해 주고받은 내용을 확인하는게 이 문제의 목표임을 짐작할 수 있습니다.

Client Hello

Client Hello는 클라이언트가 서버와 TLS 통신을 시작하기 위해 보내는 메시지입니다. 4번째 패킷이 이에 해당하며 보낸 패킷을 확인하면 아래와 같습니다.

이 Client Hello의 여러 필드 중에서 중요한 것을 짚어보면, 먼저 Cipher Suites는 클라이언트가 서버에게 암호화 방식으로 채택할 수 있는 후보군을 제공합니다. 이 패킷에는 아래와 같은 총 6개의 후보군이 제공되었고 서버는 이 중에서 무엇을 택할지 Server Hello에서 정해야 합니다.

key_share에서는 Diffie-Hellman 키 교환에서 사용할 클라이언트의 값을 보냅니다. 보통 $Z_p$에서 Diffie-Hellman 키 교환을 하는 예시를 많이 소개하지만, TLS 1.3에서는 효율성을 위해 타원 곡선 위에서 Diffie-Hellman 키 교환을 진행합니다. 이를 ECDHE(Elliptic Curve Diffie Hellman Ephemeral)이라고 합니다. Ephemeral은 매번 비밀키가 다르게 생성된다는 의미이고 위에서 언급한 Perfect forward secrecy와 같은 의미입니다.

타원 곡선 암호에 대해서 정말 간략하게만 설명을 해보면, 타원 곡선 암호는 $y^2 = x^3 + ax + b(\text{mod } p)$라는 방정식에서의 점들을 이용해 만들어내는 암호입니다. 타원 곡선 위의 임의의 점 $P, Q$를 생각할 때 $P+Q$, $nP$($n$은 정수)와 같은 연산들이 자연스럽게 정의가 됩니다. 마치 $Z_p$에서 $g, y$가 공개되어 있을 때 $g^x = y$를 만족하는 $x$를 찾기 어려운 것과 같이 ECDHE에서는 점 $G, Y$가 공개되어 있을 때 $nG = Y$를 만족하는 $n$을 찾기 어렵다는 것을 이용해 키 교환을 진행할 수 있습니다.

key_share 필드를 확인해보면 클라이언트는 서버에게 타원 곡선의 정보와, 클라이언트가 정한 점 nG를 제공합니다. 이 때 n은 클라이언트의 비밀값입니다.

사실 대놓고 스포일러이지만, 이 점이 G와 동일해서 비밀키가 곧 서버의 점인 것이 이 문제의 취약점이었습니다. rkm님이 해당 점의 값을 그냥 검색해보니 G와 일치한걸 발견할 수 있었다고 하네요.

Server Hello

Server Hello는 서버가 Client Hello 를 받은 후 클라이언트에게 보내는 메시지입니다. Server Hello가 끝난 후 부터는 서버와 클라이언트가 서로 공유한 K를 이용해 암호화된 통신을 진행합니다. 6번째 패킷이 이에 해당합니다.

실제로 Wireshark에서 해당 패킷을 확인해보면 Server Hello 뒤에 또 다른 암호화된 정보가 붙어 길이가 상당히 긴 것을 확인할 수 있는데, 이 정보는 서버의 인증서를 담고 있습니다. Server Hello를 끝낸 후에 서버는 클라이언트에게 자신의 인증서를 보내줍니다. 이 과정이 필요한 이유는 바로 현재 클라이언트가 통신을 하고 있는 대상이 google.com이 맞다는 것을 확인시켜주기 위함입니다. 공격자가 설령 통신을 중간에 가로채서 google.com인척 위장을 하고 싶다고 하더라도 인증서를 확인하는 과정에서 발각됩니다.

먼저 Cipher Suite를 보면 서버는 Client Hello에 들어있던 6가지 목록 중에서 TLS_AES_128_GCM_SHA256을 쓰기로 결정했습니다. AES_128_GCM은 대칭키 시스템으로 AES 128을 사용하고, 블록암호의 운용 모드는 GCM으로 하겠다는 의미입니다. GCMAEAD(Authenticated Encryption with Associated Data)라고 해서, 인증(Authentication)과 암호화(Encryption)을 동시에 수행해주는 운용 모드입니다. 이에 대해서도 작성하고 싶은게 많지만 다음을 기약하고 설명은 생략하겠습니다. 그리고 SHA256은 해싱을 SHA256으로 사용한다는 의미입니다.

Client와 마찬가지로 key_share 필드를 보면 서버가 정한 점을 제공합니다.

이미 클라이언트에서 정한 $n$값이 1임을 알고 있기 때문에 이 서버의 점이 바로 클라이언트와 서버가 공유하는 점이 되고, 이 점의 $x$ 좌표 값이 바로 비밀 값입니다.

Diffie-Hellman 키 교환에서 클라이언트와 서버가 공유한 비밀 정보를 알아냈기 때문에 통신을 그냥 들여다보기 위한 모든 준비가 끝났습니다. 이제 규격을 파악하고 실제 AES의 키를 어떻게 만들어내는지만 알아낸 후 복호화를 수행하면 끝인데, 공식 문서는 뭔가 보기에 상당히 불편하게 되어있었고, 구현체를 보고 흐름을 따라가려고 했지만 흐름이 잘 눈에 들어오지 않아 결국 직접 openssl로 tls 1.3 서버를 만들어 통신을 해보고, 이런저런 중간값들을 확인하는 등 엄청난 삽질을 했습니다. 그래서 정작 암호학적 취약점은 찾았음에도 불구하고 실제 플래그를 얻어내는 과정이 거의 6시간 넘게 지체되었습니다. 이 부분은 크게 중요한 부분은 아닌 것 같아 설명을 생략하겠습니다.

5. Conclusions

이번 글을 통해 TLS 1.3 버전을 알아보았습니다. 만약 네트워크와 암호학에 대한 어느 정도의 지식이 있었다면 이 글의 내용을 이해하는데에 더 도움이 됐을 것으로 추정됩니다.

참고로 현재 TLS 1.3버전의 사용이 권장되지만 그렇다고 해서 TLS 1.2에 심각한 취약점이 있는 것은 아니어서 TLS 1.2 버전이 빠른 시일내에 사용이 중단될 것으로는 보이지 않습니다.

끝으로 DecryptTLS 문제를 직접 한 번 풀어보시면 실제 TLS 1.3버전을 이해하는데 큰 도움이 될 것으로 보입니다.