EASY7
ghost(CVE-2015-0235) 본문
ghost 취약점
GNU C Library (glibc) 의 호스트네임을 조회하는 gethostbyname()의 패밀리함수에서 입력값 검증이 제대로 이루어지지 않아 Overflow 를 일으키는 버그.
관련 CVE정보
CVE-2015-0235
취약한 소프트웨어 버전
취약한 버전 : On Fedora 19 (glibc-2.17): [user@...ora-19 ~]$ ./GHOST vulnerable
패치된 버전 : On Fedora 20 (glibc-2.18): [user@...ora-20 ~]$ ./GHOST not vulnerable
보안 조치 방법
netdb.h 라이브러리의 gethostbyname 함수대한 이해
함수를 호출하면 소켓 라이브러리가 DNS서버에 조회하여 IP주소나 도메인 명을 찾는 기능을 제공한다.
정보를 hostent구조체에 담아서 반환해준다.
(참고 : http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=518686)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h>
int main( void) { struct hostent *host_entry; int ndx;
host_entry = gethostbyname( "easy7.tistory.com");
if ( !host_entry) { printf( "gethostbyname() 실행 실패/n"); exit( 1); } for ( ndx = 0; NULL != host_entry->h_addr_list[ndx]; ndx++){ printf("ndx : %d\n", ndx); printf("%x \n", host_entry->h_addr_list[ndx]); printf("\n"); printf( "%s\n", inet_ntoa( *(struct in_addr*)host_entry->h_addr_list[ndx]));
} return 0; } |
h_addr_list 배열은 데이터 하나만 들어있다
(0x5257b818) 이건... h_addr_list의 값이 아닌 메모리 주소인 것 같다.
*빅엔디언을 십진수로 변경해보기(h_addr_list의 값이 아님 ㅠㅠ)
16진수를 2진수로 변환해보겠다.
5 -> 0101
2 -> 0010
5 -> 0101
7 -> 0111
b -> 1011
8 -> 1000
1 -> 0001
8 -> 1000
2진수들을 이으면
0101 0010 , 0101 0111, 1011 1000, 0001 1000 (https://m.blog.naver.com/PostView.nhn?blogId=rna2823&logNo=220602373183&proxyReferer=https%3A%2F%2Fwww.google.com%2F)
2진수를 10진수로 변경
82,87,184,24 (땡!)
*빅엔디언을 십진수로 변경해보기(h_addr_list의 값이 아님 ㅠㅠ)
16진수를 2진수로 변환해보겠다.
5 -> 0101
2 -> 0010
5 -> 0101
7 -> 0111
b -> 1011
8 -> 1000
1 -> 0001
8 -> 1000
2진수들을 이으면
0101 0010 , 0101 0111, 1011 1000, 0001 1000 (https://m.blog.naver.com/PostView.nhn?blogId=rna2823&logNo=220602373183&proxyReferer=https%3A%2F%2Fwww.google.com%2F)
2진수를 10진수로 변경
82,87,184,24 (땡!)
* 네트워크 바이트 순서(big endian)과 십진수 표현 방식(Dotted-decimal notation)간의 상호 변환 함수.
inet_ntoa() : 네트워크 바이트 순서-> 십진수 표현 방식
inet_addr() : 십진수 표현방식 -> 네트워크 바이트 순서
* hostent 구조체(참고 : https://sjh836.tistory.com/50)
struct hostent {
char *h_name; //공식 도메인 이름
char **h_aliases; //공식 이외 도메인 이름들
int h_addrtype; //주소정보 체계(IPv4: AF_INET, IPv6: AF_INET6)
int h_length; //IP주소의 크기를 담는다. (IPv4는 4)
char **h_addr_list; //도메인 이름에 대한 IP주소가 정수 형태로 반환될 때 이 멤버 변수를 이용 }
}
* gethostbyname_r()함수
struct hostent *gethostbyname_r( const char * name, struct hostent * result, char * buffer, int bufflen, int * h_errnop );
- name : 인터넷 호스트 네임 ex) www.naver.com
- result : hostent struct 의 포인터, 결과가 담긴다
- buffer : 연산중 저장하는 name을 저장하는 임시 데이터(?)
- bufflen : buffer에 의해 포인트되는 구역의 길이
- h_errnop : 에러가 날 때 에러 넘버를 저장하는 포인터
실습 ghost 취약점 해보기
1. 취약한 버전인지 확인하기.
아래 C로 작성된 파일 실행하기
#include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h>
#define CANARY "in_the_coal_mine"
struct { char buffer[1024]; char canary[sizeof(CANARY)]; } temp = { "buffer", CANARY };
int main(void) { struct hostent resbuf; struct hostent *result; int herrno; int retval;
/*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/ size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1; //1024-16-16-1=991 사이즈 오차 char name[sizeof(temp.buffer)]; memset(name, '0', len); //name 문자열 0~990까지 0으로 대체 name[len] = '\0'; //name[991]에 문자의 끝을 나타내는 \0 대입
printf("before: %s \n", temp.canary); //이전 temp.canary 값 "in_the_coal_mine" //name을 temp.buffer에 대입하여 오버플로우 시킴. retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
if (strcmp(temp.canary, CANARY) != 0) { puts("vulnerable"); exit(EXIT_SUCCESS); } if (retval == ERANGE) { puts("not vulnerable"); exit(EXIT_SUCCESS); } puts("should not happen"); exit(EXIT_FAILURE); }
|
# gcc ghost.c -o ghost
취약한 버전 : On Fedora 19 (glibc-2.17): [user@...ora-19 ~]$ ./GHOST vulnerable
패치된 버전 : On Fedora 20 (glibc-2.18): [user@...ora-20 ~]$ ./GHOST not vulnerable
취약한 패밀리함수 __nss_hostname_digits_dots....
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
int __nss_hostname_digits_dots (const char *name, struct hostent *resbuf, char **buffer, size_t *buffer_size, size_t buflen, struct hostent **result, enum nss_status *status, int af, int *h_errnop) { if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':') { const char *cp; char *hostname; typedef unsigned char host_addr_t[16]; host_addr_t *host_addr; typedef char *host_addr_list_t[2]; host_addr_list_t *h_addr_ptrs; char **h_alias_ptr; size_t size_needed;
size_needed = (sizeof (*host_addr)+ sizeof (*h_addr_ptrs) + strlen (name) + 1);
if (buffer_size == NULL) { if (buflen < size_needed) { goto done; } } else if (buffer_size != NULL && *buffer_size < size_needed) { char *new_buf; *buffer_size = size_needed; new_buf = (char *) realloc (*buffer, *buffer_size);
if (new_buf == NULL) { goto done; } *buffer = new_buf; }
host_addr = (host_addr_t *) *buffer; h_addr_ptrs = (host_addr_list_t *) ((char *) host_addr + sizeof (*host_addr)); h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs)); hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
if (isdigit (name[0])) { for (cp = name;; ++cp) { if (*cp == '\0') { int ok;
if (*--cp == '.') break;
if (af == AF_INET) ok = __inet_aton (name, (struct in_addr *) host_addr); else { assert (af == AF_INET6); ok = inet_pton (af, name, host_addr) > 0; } if (! ok) {
goto done; }
resbuf->h_name = strcpy (hostname, name);
goto done; }
if (!isdigit (*cp) && *cp != '.') break; } }
|
cs |
참고자료
실습 자료
hostent구조체 개념
gethostbyname 함수에 대한 자료
http://forum.falinux.com/zbxe/index.php?mid=C_LIB&document_srl=518686)
'보안 공부 > 취약점 실습' 카테고리의 다른 글
path traversal을 통한 RCE (0) | 2019.09.26 |
---|---|
CVE-2014-0160 OpenSSL heartbleed 하트블리드 취약점 (0) | 2019.09.22 |
Routing Table, ARP Table, MAC Table (0) | 2019.09.04 |
Race Condition 경쟁조건 (0) | 2019.09.04 |
세션 하이재킹 hunt 실습 (0) | 2019.08.17 |