2014년 4월 20일 일요일

OpenSSL (Heartbleed) 취약점 분석

OpenSSL 프로젝트란 범용 라이브러리로 구현된 보안 프로토콜을 네트워크 규격으로 확장한 것입니다. 데이터 통신을 할 때 사용하는 네트워크 암호화 프로토콜인 TLS(Transport Layer Security)와 SSL(Secure Sockets Layer)기능을 구현한 라이브러리입니다. 이 OpenSSL은 오픈 소스로 개발되어 많은 서버 관리자들이 보안하면서 발전해 왔습니다. 4월 8일에 "HeartBleed"라는 보안 취약점이 공개되었고 이를 보안하기 위한 패치또한 함께 배포 되었습니다.
OpenSSL은 취약점인 HeartBleed가 무엇인지 분석해 보겠습니다.

OpenSSL 취약점 (CVE-2014-0160)

Codenomicon의 defensics라는 보안 툴을 개선하면서 발견되었습니다. Condenomicon과 Google의 맴버가 공개했습니다. OpenSSL의 HeartBeat(심박)에서 발견되었기 때문에 HeartBleed라고 이름이 붙였습니다.
HeartBleed가 공격 받으면 OpenSSL 라이브러디를 사용하는 프로세스의 메모리 내용이 공격자에게 누설 됩니다. 좀더 자세히 설명하면 OpenSSL 라이브러리를 사용하는 프로그램의 종류에 따라 다르지만 Apache 웹서비스의 경우 사용자의 요청 데이버와 응답 데이터 SSL 인증서의 개인 키 등입니다. 즉 라이브러리를 사용하는 프로그램이 다루는 데이터 (힙에 저장된 데이터)는 모두 노출될 수 있습니다. 예를 들면 게임의 세션 정보, ID / 패스워드 등의 인증 정보가 누설 스푸핑이 발생하며 다른 사용자의 게임 데이터를 자유롭게 사용 할 수도 있습니다.

SSL의 HeartBeat란?

HeartBeat는 문자 SSL의 활성화를 감시하기 위해서 도입한 기능입니다. 이 기능은 OpenSSL 1.0.1 부터 지원합니다. 즉 HeartBleed는 OpenSSL 1.0.1~ 1.0.1f이 공격 대상이 됩니다. 기능을 좀 더 자세히 살펴보면 클라이언트가 HELLO라고 HeartBeat를 보내면 서버에서 HELLO 라고 HeartBeat를 보내는 기능입니다. 즉 HELLO를 이용해서 서버가 활성화 되어 서비스를 제공하는 지 알아 보는 기능지요.

Heartbleed 취약점

SSL을 이용한 하트 비트에서 어떤 부분이 문제가 있었을까요? 코드를 보기 전에 그림으로 설명하겠습니다. 정상적인 경우는 HB_REQ 5 "HELLO"가 복제되어 HB_RES 5 "HELLO"가 전송되지만, 비정상적인 경우는 HB_REQ 5 "HELLO"에서 HB_REQ 10 "HELLO" 로 전송하면 코드의 취약점으로 HB_RES 10 "HELLO" + "누설정보" 가 전송됩니다.



코드 분석

Heartbleed 취약점 코드를 분석하겠습니다.
클러이언트에서 하트비트 요청 메시지를 읽는 부분입니다.
 unsigned char * p = & s-> s3-> rrec.data [0], * pl; ...... (1)
 unsigned short hbtype;
 unsigned int payload;
 unsigned int padding = 16; / * Use minimum padding * /

 · · ·
 hbtype = * p + +; ...... (2)
 n2s (p, payload); ...... (3)
 pl = p; ...... (4)
서버가 클라이언트에서 하트 비트 요청을 받으면 위의 (1)이 가리키는 s-> s3-> rrec.data라는 버퍼에 요청 메시지가 저장됩니다. 이 메시지 형식은 type 필드 (1 바이트) payload_length 필드 (2 바이트), payload 필드 (payload_length 바이트), padding 필드 (16 바이트)로 간단하게 정의합니다. 회신 메시지의 형식이 똑같습니다.
소스 코드를 보면 요청 메시지의 type 필드를 hbtype 변수에 저장하고 (2) payload_length 필드를 payload 변수에 저장합니다 (3). 그리고 마지막으로 payload 필드의 시작 주소를 pl 포인터에 저장합니다 (4). 이제 요청 메시지를 읽는 처리가 끝내고 난후에 응답할 메시지를 처리할 메모리 영역을 확보합니다.
unsigned char * buffer, * bp;
 · · ·
 buffer = OPENSSL_malloc (1 + 2 + payload + padding); ...... (5)
 bp = buffer; ...... (6)
OPENSSL_malloc () 함수는 1 (type 필드의 길이) + 2 (payload_length 필드의 길이) + payload (요청 메시지 payload_length 필드의 값) + padding (16 바이트)로 메모리를 확보합니다 (5). 이후 할당 받은 메모리의 처음 어드레스를 bp 포인터로 설정합니다 (6). 마지막으로, 할당 받은 메모리에 응답 메시지를 옴깁니다.
* bp + = TLS1_HB_RESPONSE; ...... (7)
 s2n (payload, bp); ...... (8)
 memcpy (bp, pl, payload); ...... (9)
type 필드로 TLS1_HB_RESPONSE을 저장합니다 (7). 그리고 payload_length 필드로 payload 변수의 값 (즉 요청 메시지 payload_length 필드의 값)을 넣습니다. (8). 마지막으로, memcpy 함수에서 pl 포인터 (요청 메시지의 payload 필드)에서 payload 변수의 값 (요청 메시지 payload_length 필드의 값)만큼 복사합니다. 나머지는 패딩으로 16 바이트 추가 하고 응답 메시지를 회신하지만 추가 설명은불필요하다고 생각해서 생략합니다.
하트비트의 구현은 아주 간단한 코드로 되어 있어서 언듯 보면 별다른 문제가 없어 보입니다. 하지만 만약 요청 메시지의 payload 필드가 짧은에도 불구하고 payload_length 필드에 거대한 값이 지정되어 있다면 어떻게 될까요? 예를 들면 요청 메시지 payload_length 필드가 1000 바이트 payload 필드가 hello (5 바이트)이었다고 합시다. 앞에서 언급 한 요청 메시지를 읽고 처리합니다.
클라이언트의 요청 메시지가 s-> s3-> rrec.data에 저장됩니다.
 unsigned char * p = & s-> s3-> rrec.data [0], * pl; ...... (1)
 unsigned short hbtype;
 · · ·
 hbtype = * p + +; ...... (2)
 n2s (p, payload); ...... (3)
 pl = p; ...... (4)
payload 변수의 값이 1000 바이트입니다. pl 포인터에 "hello"의 시작 주소를 가리킵니다. 여기까지는 문제가 없습니다.
 unsigned char * buffer, * bp;
 · · ·
 buffer = OPENSSL_malloc (1 + 2 + payload + padding); ...... (5)
 bp = buffer; ...... (6)
그런 다음 응답 데이터를 위한 메모리로 1 + +1000 +16이 확보합니다. 아직까지는 문제가 없어 보입니다.
 * bp + = TLS1_HB_RESPONSE; ...... (7)
 s2n (payload, bp); ...... (8)
 memcpy (bp, pl, payload); ...... (9)
응답 메시지의 처리로 이동합니다. 응답 메시지의 payload_length 필드에 1000이 포함됩니다 (8). 여기까지는 아직 문제 없습니다만, 여기서부터가 문제입니다. (9)에서 memcpy () 함수를 이용하여 pl 포인터에서 1000 바이트를 payload 필드에 복사하기때문에 pl 포인터가 가리키는 메모리는 "hello"의 5 바이트로 부족합니다. 즉 pl포인터가 가리키는 5바이트 데이터와 관계없는 메모리의 995 바이트까지 포함됩니다.

누설된 최대 정보량

요청 메시지 payload_length 필드는 2 바이트이므로, 최대 65535 바이트의 메모리에 데이터가 누출될 수 있습니다. 우연히 요청 메시지로 확보 한 메모리 영역 아래부터 최대 65535 바이트까지의 영역에 중요한 데이터가 있다면 노출되어 위험할 수 있습니다. 물론 공격자 관점에서 보면, 요청 메시지가 저장될 메모리 주소를 컨트롤 할 수 없기 때문에 어떤 데이터를 얻을 수 있는지 알수 없어서 운 맡길 것입니다.

heartbeed 취약점 패치

그럼 취약점을 막은 패치를 보겠습니다.
if (1 + 2 + 16> s-> s3-> rrec.length) ...... (10)
 return 0; / * silently discard * /
 hbtype = * p + +;
 n2s (p, payload);
 if (1 + 2 + payload +16> s-> s3-> rrec.length) ...... (11)
 return 0; / * silently discard per RFC 6520 sec. 4 * /
 pl = p;
s-> s3-> rrec.length는 수신된 요청 메시지는 실제 메시지 길이 (변경 불가)가 되므로, (11)처럼 응답 메시지로 반환되는 메시지 길이가 요청 메시지의 실제 메시지 길이보다 긴 경우는 삭제하도록 바뀌었습니다. 참고로 (10)는 본 취약점은 관계 없지만, SSL 하트 비트 요청 메시지의 사양을 따르지 않는 짧은 메시지에 응답하게 됩니다.

맺음말

요며칠 세상을 떠들게 했던 OpenSSL Heartbleed 보안 관련 이슈가 어떻것인지와 이를 소스코드로 분석해 보았습니다. 사소해 보이는 작은것이지만 외부로 노출 될 수 있는 부분에 대하여 좀 더 꼼꼼한 검토가 필요하다는 교훈을 얻었네요. 위의 패치에서 보듯이 쉽게 패치가 가능합니다. 그리고 여러 리눅스 배포판으로 배포되었으니 조그만한 수고가 보안의 위협으로 부터 소중한 자원을 지킵니다.

댓글 없음:

댓글 쓰기