y0u_bat

GHOST: glibc gethostbyname buffer overflow 번역 목차 (2/6) 본문

System

GHOST: glibc gethostbyname buffer overflow 번역 목차 (2/6)

유뱃 2016. 2. 5. 03:48


발번역입니다...



원문: http://www.openwall.com/lists/oss-security/2015/01/27/9


--[ 목차 ]---------------------------------------------------------------- 1 - 서론 2 - 분석 3 - 정상 침작 요인 (Mitigating factors) 4 - 취약점 존재여부 및 사례 연구 5 - 공격 6 - 결론

--[ 1 - 서론 ]-------------------------------------------------------------

Qualys에서 내부 코드에 대해 검사를 하는 동안, 우리는 GNU C 라이브러리인 glibc안에 __nss_hostname_digits_dots() 함수 내부에서 버퍼 오버플로우가 발견했다.


이버그는 gethostbyname *() 함수의 내부의 가까이 존재하고 있다. 그래서 우리는 gethostbyname을 분석하기로 결정하였다.

그리고 이것은 엄청 영향이 있었다. 이 취약점에 이름을 "GHOST" 라고 한다.



중심내용:


- gethostbyname() 과 gethostbyname2() 를 통해서 힙영역 버퍼에 오버플로우가 가능하며,

gethostbyname_r() 혹은 gethostbyname2_r() 는 사용자가 제공하는 버퍼를 덮을 쓸 수 있다.


- sizeof(char *) 바이트만큼 덮어 쓸 수 있다. (32비트 운영체제 경우엔 4바이트, 64바이트 운영체제 경우엔 8바이트)

   해당내용은 오직 숫자 0~9, 점(.)으로 되어 있어야 한다. 그리고 끝은 null로 되어 있어야 된다.


- 이런 제한에도 불구하고, 임의적인 코드를 실행하는데 성공하였다. 우리는 개발된 Exim 메일서버를 리모트 공격에 하는데 있어 불리하게 하는 기존의 보호기법 ( aslr, pie, nx) 을  32비트 운영체제와 64비트 운영체제 둘다 우회 가능함을 증명 할 수 있다.

우리는 조만간에 메타스플로잇 모듈에 우리의 익스플로잇을 발표 할 것입니다.


- 첫번째로 취약한 버전은 GNU C 라이브러리의 glibc-2.2 릴리즈 2000.11.10 이다


- 우리는 확인된 취약점은 이버그의 영향을 완화시킨다. 

   특히, 우리가 발견한 저 취약점은 2013년 5월 21일에 패치되었다. ( gdblic-2.17 ~ glibc-2.18 )

   불행하게도 이것은 보안위혐이 알려지지 않았다. 결과적으로 매우 안정적이며, 오랜기간 노출되어 배포됬다.

   (여전히 영향받는 버전: 데비안7, 레드헷 기업용 리눅스 6 & 7, 센토스 6 & 7, 우분투 12.04) 


--[ 2 - 분석 ]------------------------------------------------------------

그 취약한 함수 __nss_hostname_digits_dots()는 내부적으로 glibc의 nss/getXXbyYY.c 와 nss/getXXbyYY_r.c을 호출한다.

그러나 그 호출들은 #ifdef HANDLE_DIGITS_DOTS 매크로 정의가 되어있다.


해당 매크로 정의하고 있는 파일

- inet/gethstbynm.c

- inet/gethstbynm2.c

- inet/gethstbynm_r.c

- inet/gethstbynm2_r.c

- nscd/gethstbynm3_r.c


그 파일들은 gethostbyname* () 류의 함수를 실행한다. 이런 이유로 오직 __nss_hostname_digits_dots()에서 일어나는 버퍼오버플로우가 유일한 방법이다.


이코드는 glibc-2.17 이다:

 35 int
 36 __nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
 37                             char **buffer, size_t *buffer_size,
 38                             size_t buflen, struct hostent **result,
 39                             enum nss_status *status, int af, int *h_errnop)
 40 {
 ..
 57   if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
 58     {
 59       const char *cp;
 60       char *hostname;
 61       typedef unsigned char host_addr_t[16];
 62       host_addr_t *host_addr;
 63       typedef char *host_addr_list_t[2];
 64       host_addr_list_t *h_addr_ptrs;
 65       char **h_alias_ptr;
 66       size_t size_needed;
 ..
 85       size_needed = (sizeof (*host_addr)
 86                      + sizeof (*h_addr_ptrs) + strlen (name) + 1);
 87
 88       if (buffer_size == NULL)
 89         {
 90           if (buflen < size_needed)
 91             {
 ..
 95               goto done;
 96             }
 97         }
 98       else if (buffer_size != NULL && *buffer_size < size_needed)
 99         {
100           char *new_buf;
101           *buffer_size = size_needed;
102           new_buf = (char *) realloc (*buffer, *buffer_size);
103
104           if (new_buf == NULL)
105             {
...
114               goto done;
115             }
116           *buffer = new_buf;
117         }
...
121       host_addr = (host_addr_t *) *buffer;
122       h_addr_ptrs = (host_addr_list_t *)
123         ((char *) host_addr + sizeof (*host_addr));
124       h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
125       hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
126
127       if (isdigit (name[0]))
128         {
129           for (cp = name;; ++cp)
130             {
131               if (*cp == '\0')
132                 {
133                   int ok;
134
135                   if (*--cp == '.')
136                     break;
...
142                   if (af == AF_INET)
143                     ok = __inet_aton (name, (struct in_addr *) host_addr);
144                   else
145                     {
146                       assert (af == AF_INET6);
147                       ok = inet_pton (af, name, host_addr) > 0;
148                     }
149                   if (! ok)
150                     {
...
154                       goto done;
155                     }
156
157                   resbuf->h_name = strcpy (hostname, name);
...
194                   goto done;
195                 }
196
197               if (!isdigit (*cp) && *cp != '.')
198                 break;
199             }
200         }
85~86라인을 보면 size_needed에 host_addr, h_addr_ptrs, name를 크기를 계산한다.

121~125라인을 보면 host_addr, h_addr_ptrs, h_alias_ptr,hostname 4개의 포인터를 준비한다.

size_needed을 계산할때sizeof(*h_alias_ptr) 만큼 빼먹은 실수가 있다.


그러므로 157라인에서 strcpy()할때 버퍼를 32비트운영체제 경우 4바이트, 64비트 운영체제 경우 8바이트를 오버플로우 시킬수있다.


오버플로우를 하기 위한 조건


- 첫번째 문자열이 숫자여야 된다. (라인127)

- 마지막 문자열이 점이면 안된다. (라인135)

- 오직숫자와 점으로 구성되야 된다.

- 오버플로우 할 버퍼가 충분이 거야 된다. 예를 들어, gethostbyname() 함수는 처음에 버퍼에 1024만큼 malloc 해준다.

- inet_athon() 함수에서 성공적으로 IPv4 주소가 성공적으로 파싱되야 된다.







Comments