티스토리 뷰
전 세계 인터넷은 OpenSSL의 중대한 버그로 난리다. 이 버그의 주요 요지는 특정 OpenSSL을 사용할 경우 메모리의 64KB를 획득할 수 있고, 이 버그로 서버 인증서의 비밀키(개인키)를 취득하여 서버로 오가는 모든 패킷을 취득할 수 있다. 라는 것인데, 이를 가리켜 Heartbleed(심장출혈) 버그라고 한다.
그 만큼 심각한 버그가 맞는데, 일각의 미디어에서 최악의 시나리오를 너무 일반화시키는 것이 아닌가 싶다.
필자가 보안 전문가는 아닌 만큼 잘못된 부분은 너그러이 지적해 주길 바란다.
- 사용자는 모두 패스워드를 변경해야 하나?
해도 되고 안해도 된다. 어차피 개인정보는 오픈소스. :)
농담이고, 취약성이 있는 OpenSSL을 사용하면 공격자는 클라이언트의 사용자 요청 데이터를 가로챌 수 있다.
공격자가 사용자의 패스워드를 가로채려면, 서버 인증서의 비밀키를 취득한 경우에 해당 된다. 하지만 heartbleed 버그는 인증서의 비밀키가 없이도 서버의 메모리 최대 64KB를 볼 수 있다. 모든 데이터를 가로채는 것은 아니고 64KB에 해당하는 찌꺼기(?) 영역인데, 이 영역에 마지막 클라이언트의 요청 데이터가 저장되어 있다.
그러므로 자주 방문하는 사이트면 비밀번호를 변경하는 것이 좋고, 1년 넘게 방문하지 않은 사이트는 변경하지 않아도 된다.
- 서버 운영자는 인증서를 모두 폐기해야 하나?
취약한 버전의 OpenSSL을 사용하고, 최근 서버를 restart 한 경우가 아니라면 거의 제로(0)에 가깝다.
공격자가 64KB 중에서 인증서의 비밀키를 훔치기 위해서는 대상 서버를 재가동하고 첫 번째 요청인 경우에 이 비밀키를 훔쳐갈 수 있는 가능성이 높아진다고 한다. 이는 Answering the Critical Question: Can You Get Private SSL Keys Using Heartbleed? 에서 실험한 결과이다.
- OpenSSL 업그레이드가 불가능할 경우
소스 코드를 보면 곳곳에 아래와 같이 #ifndef OPENSSL_NO_HEARTBEATS
지시자를 발견 할 수 있다. 그러므로 OpenSSL 을 OPENSSL_NO_HEARTBEATS 옵션과 함께 다시 컴파일 하면 heartbleed 취약성 버그를 해결할 수 있다.
#ifndef OPENSSL_NO_HEARTBEATS
int
tls1_process_heartbeat(SSL *s) {
...
...
}
int
tls1_heartbeat(SSL *s) {
...
...
}
#endif
- OpenSSL 코드 품질
Heartbleed 취약성 버그가 해결된 7e840163 커밋을 살펴보면, 아직도 여전히 코드 리뷰를 통해 이슈가 남아있다.
코드 측면에서 변수의 이름이 'payload 는 payload_length 가 되어야 하지 않느냐' 라는 의견이 있다. 그리고 padding 값이 16으로 초기화가 되었음에도 곳곳에 하드 코딩된 '16' 값을 찾아볼 수 있다.
가장 최신 커밋에는 코드 리뷰가 완료되었는 지 모르겠으나, 당시 취약성 버그로 상당히 급하게 코드를 수정한 것 같다는 느낌을 받을 수 있었다.
OpenSSL 디버깅
Heartbeat 프로토콜 Heartbeat network, Linux-HA에서 알 수 있듯이 클러스터링(clustering) 및 고가용성(high-availability linux)을 위해 서버끼리 주고 받는 메시지라고 한다. active, standby 서버 두 대 중 active 서버가 죽으면 standby 가 가동되어 장애를 최소화 하는데, 이 때 ‘죽었니 살았니’ 빼꼼 찔러보는 걸 heartbeat 라고 한다고 한다.
실제 필자의 클라우드 서버에서 테스트를 진행하려고 했으나 여건이 되지 않아 실제 환경과 유사하게 테스트는 하지 못했다.
먼저, github의 OpenSSL 소스 코드를 받고, 버그가 있는 tag 및 branch를 checkout 한다. 이어 디버그 모드로 컴파일을 하면 테스트 준비는 완료된다. 그리고 heartbeat 패킷을 보내줄 수 있는 github의 pacemaker 클라이언트 코드를 받는다.
호스팅된 openssl, lldb attaching
디버그 모드로 컴파일된 openssl 을 self-hosting으로 실행한다.
$ lldb openssl s_server -www
그리고 openssl/ssl/t1_lib.c 소스 코드의 tls1_process_heartbeat
함수에 브레이크 포인트를 걸었다.
(lldb) br list
Current breakpoints:
1: name = 'tls1_process_heartbeat', locations = 1, resolved = 1, hit count = 3
1.1: where = openssl`tls1_process_heartbeat + 21 at t1_lib.c:2484, address = 0x000aff55, resolved, hit count = 3
이제 pacemarker 를 통해 heartbeat를 보냈다.
$ ./heartbleed.py -p 4433 -t 100000 localhost
t1_lib.c 로컬 변수
다음의 코드 중 &s->s3->rrec.data[0]
는 incoming 데이터가 포함 된다.
int
tls1_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 */
함수의 매개변수로 SSL *s
구조체의 s3 구조체의 데이터는 다음과 같다. s3->rrec
가 클라이언트에서 보낸 데이터가 되겠다. 이 구조체는 다음과 같은 값을 가지고 있다.
(lldb) e *s->s3
(ssl3_state_st) $58 = {
flags = 0
delay_buf_pop_ret = 0
read_sequence = ""
read_mac_secret_size = 0
read_mac_secret = ""
write_sequence = ""
write_mac_secret_size = 0
write_mac_secret = ""
server_random = "SN\x91ki��E\x82V\x01%E\v[t�7�\x91\x88\x9e[�\x8af\x95\x92iU"
client_random = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
need_empty_fragments = 0
empty_fragment_done = 0
init_extra = 0
rbuf = (buf = "\x16\x03\x01\x18\x03\x01", len = 17736, offset = 11, left = 0)
wbuf = (buf = "", len = 17584, offset = 12, left = 0)
rrec = (type = 24, length = 3, off = 0, data = "\x01��|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", input = "\x01��|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", comp = 0x00000000, epoch = 0, seq_num = "")
wrec = (type = 22, length = 9, off = 0, data = "\x0e", input = "\x0e", comp = 0x00000000, epoch = 0, seq_num = "")
alert_fragment = ""
alert_fragment_len = 0
handshake_fragment = ""
handshake_fragment_len = 0
wnum = 0
wpend_tot = 4
wpend_type = 22
wpend_ret = 4
wpend_buf = 0x0236c800 "\x0e"
handshake_buffer = 0x00000000
handshake_dgst = 0x007071e0
change_cipher_spec = 0
warn_alert = 0
fatal_alert = 0
alert_dispatch = 0
send_alert = ""
renegotiate = 0
total_renegotiations = 0
num_renegotiations = 0
in_read_app_data = 0
client_opaque_prf_input = 0x00000000
client_opaque_prf_input_len = 0
server_opaque_prf_input = 0x00000000
server_opaque_prf_input_len = 0
tmp = {
cert_verify_md = ""
finish_md = ""
finish_md_len = 0
peer_finish_md = ""
peer_finish_md_len = 0
message_size = 124
message_type = 1
new_cipher = 0x002ca8d0
dh = 0x00000000
ecdh = 0x00706dc0
next_state = 8576
reuse_message = 0
cert_req = 0
ctype_num = 0
ctype = ""
ca_names = 0x00000000
use_rsa_tmp = 0
key_block_length = 0
key_block = 0x00000000
new_sym_enc = 0x00000000
new_hash = 0x00000000
new_mac_pkey_type = 0
new_mac_secret_size = 0
new_compression = 0x00000000
cert_request = 0
}
previous_client_finished = ""
previous_client_finished_len = '\0'
previous_server_finished = ""
previous_server_finished_len = '\0'
send_connection_binding = 0
next_proto_neg_seen = 0
}
openssl 코드에서 2491: n2s(p, payload);
가 클라이언트에서 요청한 payload 인데 이 값은 다음과 같다.
(lldb) fr v payload
(unsigned int) payload = 65517
실제 요청된 값과 길이를 체크하지 않은 채 아래와 같이 바로 메모리를 할당하게 되는데
2505 * message type, plus 2 bytes payload length, plus
2506 * payload, plus padding
2507 */
-> 2508 buffer = OPENSSL_malloc(1 + 2 + payload + padding);
2509 bp = buffer;
2510
2511 /* Enter response type, length and copy payload */
아래의 코드의 함수가 실행되면서, 위에서 할당된 메모리의 65536 bytes (=1+2+payload+padding) 를 buffer에 쓰면서 클라이언트로 64KB 의 over-read 된 메모리의 데이터까지 클라이언트에 response 된다.
2516 /* Random padding */
2517 RAND_pseudo_bytes(bp, padding);
2518
-> 2519 r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
2520
2521 if (r >= 0 && s->msg_callback)
2522 s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
(lldb) fr v
(SSL *) s = 0x00469c10
(unsigned char *) p = 0x012f8a0b "|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
(unsigned char *) pl = 0x012f8a0b "|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
(unsigned short) hbtype = 1
(unsigned int) payload = 65517
(unsigned int) padding = 16
(unsigned char *) buffer = 0x01301600 "\x02��|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
(unsigned char *) bp = 0x01301603 "|\x03\x01BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
(int) r = -1
메모리가 확보되는 buffer = OPENSSL_malloc(...)
는 함수부 선언의 unsigned char *p = &s->s3->rrec.data[0]
의 &p
메모리 위치 근처에(&p
주소보다 더 높은 주소) 확보가 된다. 위에서 &p
는 incoming 데이터가 있다고 언급했다.
그러므로 heartbeat의 response의 값은 가장 최근에 남아 있는 incoming 데이터, 즉 클라이언트 요청 데이터의 찌꺼기가 남아있는데, over-read 버그로 인해 이 영역의 사용자 요청 데이터가 전송되게 된다.
이렇게 공격자가 훔친 데이터는 아래와 같이 클라이언트 요청 정보가 포함된다. 일반적으로 클라이언트가 서버로 요청하는 정보는 HTTP 프로토콜에 포함되는 URI, Header, Cookie 등을 가로챌 수 있다.
아래는 64KB 범위 안에서 클라이언트가 보낸 incoming 찌꺼기가 남은 64KB 메모리 값의 일부분이다.
(lldb) m r p --count 250
0x0237300b: 7c 03 01 42 42 42 42 42 42 42 42 42 42 42 42 42 |..BBBBBBBBBBBBB
0x0237301b: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0x0237302b: 42 42 42 00 00 4e c0 30 c0 28 c0 14 00 9f 00 6b BBB..N�0�(�....k
0x0237303b: 00 39 00 88 c0 32 c0 2e c0 2a c0 26 c0 0f c0 05 .9..�2�.�*�&�.�.
0x0237304b: 00 9d 00 3d 00 35 00 84 c0 12 00 16 c0 0d c0 03 ...=.5..�...�.�.
0x0237305b: 00 0a c0 2f c0 27 c0 13 00 9e 00 67 00 33 00 45 ..�/�'�....g.3.E
0x0237306b: c0 31 c0 2d c0 29 c0 25 c0 0e c0 04 00 9c 00 3c �1�-�)�%�.�....<
0x0237307b: 00 2f 00 41 01 00 00 05 00 0f 00 01 01 2f 32 30 ./.A........./20
0x0237308b: 31 30 30 31 30 31 20 46 69 72 65 66 6f 78 2f 32 100101 Firefox/2
0x0237309b: 38 2e 30 0d 0a 41 63 63 65 70 74 3a 20 74 65 78 8.0..Accept: tex
0x023730ab: 74 2f 68 74 6d 6c 2c 61 70 70 6c 69 63 61 74 69 t/html,applicati
0x023730bb: 6f 6e 2f 78 68 74 6d 6c 2b 78 6d 6c 2c 61 70 70 on/xhtml+xml,app
0x023730cb: 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 3b 71 3d 30 lication/xml;q=0
0x023730db: 2e 39 2c 2a 2f 2a 3b 71 3d 30 2e 38 0d 0a 41 63 .9,*/*;q=0.8..Ac
0x023730eb: 63 65 70 74 2d 4c 61 6e 67 75 61 67 65 3a 20 6b cept-Language: k
0x023730fb: 6f 2d 6b 72 2c 6b 6f 3b 71 3d o-kr,ko;q=
'C++' 카테고리의 다른 글
[Swift] Xcode 6.3, Swift 1.2 업그레이드 시 언어 사양이 변경된 부분 정리 (0) | 2015.04.23 |
---|---|
[Redis] 새로 추가한 mysql 명령어로 db 연동 (0) | 2014.04.21 |
[GDB] Hopper Disassembler 앱 (0) | 2014.02.12 |
[퀴즈] 프로그래머를 위한 문제 #4 - 또라이 같은 C 언어 코드를 설명하라 (0) | 2013.12.30 |
[Objective-C] OSX 매버릭스(Mavericks) 업데이트 후 개발 중인 앱이 정상 작동하지 않는다면 (0) | 2013.10.25 |
- Total
- Today
- Yesterday
- ***** MY SOCIAL *****
- [SOCIAL] 페이스북
- [SOCIAL] 팀 블로그 트위터
- .
- ***** MY OPEN SOURCE *****
- [GITHUB] POWERUMC
- .
- ***** MY PUBLISH *****
- [MSDN] e-Book 백서
- .
- ***** MY TOOLS *****
- [VSX] VSGesture for VS2005,200…
- [VSX] VSGesture for VS2010,201…
- [VSX] Comment Helper for VS200…
- [VSX] VSExplorer for VS2005,20…
- [VSX] VSCmd for VS2005,2008
- .
- ***** MY FAVORITES *****
- MSDN 포럼
- MSDN 라이브러리
- Mono Project
- STEN
- 일본 ATMARKIT
- C++ 빌더 포럼
- .
- Visual Studio 2008
- Visual Studio
- TFS 2010
- testing
- 비주얼 스튜디오 2010
- c#
- Visual Studio 11
- Team Foundation Server
- Visual Studio 2010
- Windows 8
- monodevelop
- ALM
- 땡초
- .NET
- Managed Extensibility Framework
- TFS
- ASP.NET
- mono
- .NET Framework 4.0
- Team Foundation Server 2010
- 비주얼 스튜디오
- Silverlight
- test
- 엄준일
- 팀 파운데이션 서버
- github
- MEF
- umc
- LINQ
- POWERUMC