기본 콘텐츠로 건너뛰기

CVE-2016-5180

CVE-2016-5180 1-byte-write-heap-buffer-overflow in c-ares

c-ares는 비동기 DNS 요청을 위한 C 라이브러리이다. c-ares에서 발견된 heap buffer overflow 취약점 CVE-2016-5180을 알아보자.

target.cc

c-ares 퍼징 테스트를 위해 작성된 target.cc 파일을 먼저 살펴보자.

#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <arpa/nameser.h>

#include <ares.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t e) {
  unsigned char *buf;
  int buflen;
  std::string s(reinterpret_cast<const char *>(Data), Size);
  ares_create_query(s.c_str(), ns_c_in, ns_t_a, 0x1234, 0, &buf, buflen, 0);
  free(buf);
  return 0;
}

ares_create_query함수 인자 값으로 Data 값을 캐스팅하여 전달한다.

ares_create_query : 단일 DNS 쿼리 버퍼 작성
reinterpret_cast : 포인터를 다른 포인터 형식으로 변환
string.c_str() : string 문자열의 첫번째 문자의 주소를 반환

이제 빌드하여 분석을 해보자.

mkdir -p ~/c-ares-CVE-2016-5180; rm -rf ~/c-ares-CVE-2016-5180; cd ~/c-ares-CVE-2016-5180
~/FTS/c-ares-CVE-2016-5180/build.sh 

clang -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div

CVE-2016-5180

Analysis

==20373:20373==ERROR: AddressSanitizer: heap-buffer-overflow on address >0x60400003faf1 at pc 0x0000004ec456 bp 0x7ffd4f99c210 sp 0x7ffd4f99c208
WRITE of size 1 at 0x60400003faf1 thread T0
#0 0x4ec455 in ares_create_query /root/c-ares-CVE-2016-5180/BUILD/>ares_create_query.c:196:3
#1 0x4eb73d in LLVMFuzzerTestOneInput /root/FTS/c-ares-CVE-2016-5180/>target.cc:14:3
#2 0x4f5e93 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, >unsigned long) /root/Fuzzer/FuzzerLoop.cpp:493:13
#3 0x4f60c0 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned >long) /root/Fuzzer/FuzzerLoop.cpp:450:3
#4 0x4f73fb in fuzzer::Fuzzer::MutateAndTestOne() /root/Fuzzer/>FuzzerLoop.cpp:700:30
#5 0x4f7657 in fuzzer::Fuzzer::Loop() /root/Fuzzer/>FuzzerLoop.cpp:732:5
#6 0x4eecb4 in fuzzer::FuzzerDriver(int*, char***, int ()(unsigned >char const, unsigned long)) /root/Fuzzer/FuzzerDriver.cpp:567:6
#7 0x4ec790 in main /root/Fuzzer/FuzzerMain.cpp:20:10
#8 0x7f5f2f60e82f in __libc_start_main (/lib/x86_64-linux-gnu/>libc.so.6+0x2082f)
#9 0x41c5d8 in _start (/root/c-ares-CVE-2016-5180/>c-ares-CVE-2016-5180+0x41c5d8)

0x60400003faf1 is located 0 bytes to the right of 33-byte region [>0x60400003fad0,0x60400003faf1)
allocated by thread T0 here:
#0 0x4bd893 in __interceptor_malloc (/root/c-ares-CVE-2016-5180/>c-ares-CVE-2016-5180+0x4bd893)
#1 0x4ebc55 in ares_create_query /root/c-ares-CVE-2016-5180/BUILD/>ares_create_query.c:133:10
#2 0x4eb73d in LLVMFuzzerTestOneInput /root/FTS/c-ares-CVE-2016-5180/>target.cc:14:3
#3 0x4f5e93 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, >unsigned long) /root/Fuzzer/FuzzerLoop.cpp:493:13
#4 0x4f60c0 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned >long) /root/Fuzzer/FuzzerLoop.cpp:450:3
#5 0x4f73fb in fuzzer::Fuzzer::MutateAndTestOne() /root/Fuzzer/>FuzzerLoop.cpp:700:30
#6 0x4f7657 in fuzzer::Fuzzer::Loop() /root/Fuzzer/>FuzzerLoop.cpp:732:5
#7 0x4eecb4 in fuzzer::FuzzerDriver(int*, char***, int ()(unsigned >char const, unsigned long)) /root/Fuzzer/FuzzerDriver.cpp:567:6
#8 0x4ec790 in main /root/Fuzzer/FuzzerMain.cpp:20:10
#9 0x7f5f2f60e82f in __libc_start_main (/lib/x86_64-linux-gnu/>libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /root/c-ares-CVE-2016-5180>/BUILD/ares_create_query.c:196:3 in ares_create_query
Shadow bytes around the buggy address:
0x0c087fffff00: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fa
0x0c087fffff10: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
0x0c087fffff20: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fd
0x0c087fffff30: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
0x0c087fffff40: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
=>0x0c087fffff50: fa fa fd fd fd fd fd fd fa fa 00 00 00 00[01]fa
0x0c087fffff60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fffff70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fffff80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fffff90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fffffa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==20373:20373==ABORTING
MS: 2 ChangeByte-CrossOver-; base unit: >13c2ef7d0465439beabceb4941ba56718bec2752
0xa,0xff,0xff,0x5c,0xa,0x1,0x2e,0x1,0x2e,0xa,0xff,0xff,0x52,0x52,0x52,0x5c>,0x5c,0x5c,0x2e,
\x0a\xff\xff\\x0a\x01.\x01.\x0a\xff\xffRRR\\\.
artifact_prefix=’./’; Test unit written to ./>crash-0f2607879238b3c5c96020fa0cd2664d91928fff
Base64: Cv//XAoBLgEuCv//UlJSXFxcLg==

Fix

 int ares_create_query(const char *name, int dnsclass, int type,
-                      unsigned short id, int rd, unsigned char **buf,
-                      int *buflen, int max_udp_size)
+                      unsigned short id, int rd, unsigned char **bufp,
+                      int *buflenp, int max_udp_size)
 {
-  int len;
+  size_t len;
   unsigned char *q;
   const char *p;
+  size_t buflen;
+  unsigned char *buf;

   /* Set our results early, in case we bail out early with an error. */
-  *buflen = 0;
-  *buf = NULL;
+  *buflenp = 0;
+  *bufp = NULL;

-  /* Compute the length of the encoded name so we can check buflen.
-   * Start counting at 1 for the zero-length label at the end. */
-  len = 1;
-  for (p = name; *p; p++)
-    {
-      if (*p == '\\' && *(p + 1) != 0)
-        p++;
-      len++;
-    }
-  /* If there are n periods in the name, there are n + 1 labels, and
-   * thus n + 1 length fields, unless the name is empty or ends with a
-   * period.  So add 1 unless name is empty or ends with a period.
+  /* Allocate a memory area for the maximum size this packet might need. +2
+   * is for the length byte and zero termination if no dots or ecscaping is
+   * used.
    */
-  if (*name && *(p - 1) != '.')
-    len++;
-
-  /* Immediately reject names that are longer than the maximum of 255
-   * bytes that's specified in RFC 1035 ("To simplify implementations,
-   * the total length of a domain name (i.e., label octets and label
-   * length octets) is restricted to 255 octets or less."). We aren't
-   * doing this just to be a stickler about RFCs. For names that are
-   * too long, 'dnscache' closes its TCP connection to us immediately
-   * (when using TCP) and ignores the request when using UDP, and
-   * BIND's named returns ServFail (TCP or UDP). Sending a request
-   * that we know will cause 'dnscache' to close the TCP connection is
-   * painful, since that makes any other outstanding requests on that
-   * connection fail. And sending a UDP request that we know
-   * 'dnscache' will ignore is bad because resources will be tied up
-   * until we time-out the request.
-   */
-  if (len > MAXCDNAME)
-    return ARES_EBADNAME;
-
-  *buflen = len + HFIXEDSZ + QFIXEDSZ + (max_udp_size ? EDNSFIXEDSZ : 0);
-  *buf = ares_malloc(*buflen);
-  if (!*buf)
-      return ARES_ENOMEM;
+  len = strlen(name) + 2 + HFIXEDSZ + QFIXEDSZ +
+    (max_udp_size ? EDNSFIXEDSZ : 0);
+  buf = ares_malloc(len);
+  if (!buf)
+    return ARES_ENOMEM;

   /* Set up the header. */
-  q = *buf;
+  q = buf;
   memset(q, 0, HFIXEDSZ);
   DNS_HEADER_SET_QID(q, id);
   DNS_HEADER_SET_OPCODE(q, QUERY);
   if (rd) {
     DNS_HEADER_SET_RD(q, 1);
@@ -157,23 +131,27 @@ int ares_create_query(const char *name, int dnsclass, int type,

   /* Start writing out the name after the header. */
   q += HFIXEDSZ;
   while (*name)
     {
-      if (*name == '.')
+      if (*name == '.') {
+        free (buf);
         return ARES_EBADNAME;
+      }

       /* Count the number of bytes in this label. */
       len = 0;
       for (p = name; *p && *p != '.'; p++)
         {
           if (*p == '\\' && *(p + 1) != 0)
             p++;
           len++;
         }
-      if (len > MAXLABEL)
+      if (len > MAXLABEL) {
+        free (buf);
         return ARES_EBADNAME;
+      }

       /* Encode the length and copy the data. */
       *q++ = (unsigned char)len;
       for (p = name; *p && *p != '.'; p++)
         {
@@ -193,16 +171,32 @@ int ares_create_query(const char *name, int dnsclass, int type,

   /* Finish off the question with the type and class. */
   DNS_QUESTION_SET_TYPE(q, type);
   DNS_QUESTION_SET_CLASS(q, dnsclass);

+  q += QFIXEDSZ;
   if (max_udp_size)
   {
-      q += QFIXEDSZ;
       memset(q, 0, EDNSFIXEDSZ);
       q++;
       DNS_RR_SET_TYPE(q, T_OPT);
       DNS_RR_SET_CLASS(q, max_udp_size);
+      q += (EDNSFIXEDSZ-1);
+  }
+  buflen = (q - buf);
+
+  /* Reject names that are longer than the maximum of 255 bytes that's
+   * specified in RFC 1035 ("To simplify implementations, the total length of
+   * a domain name (i.e., label octets and label length octets) is restricted
+   * to 255 octets or less."). */
+  if (buflen > (MAXCDNAME + HFIXEDSZ + QFIXEDSZ +
+                (max_udp_size ? EDNSFIXEDSZ : 0))) {
+    free (buf);
+    return ARES_EBADNAME;
   }

+  /* we know this fits in an int at this point */
+  *buflenp = (int) buflen;
+  *bufp = buf;
+
   return ARES_SUCCESS;
 }
-- 
2.9.3

이 블로그의 인기 게시물

데일 카네기 인간관계론 정리

Remove-Server-Header

응답 메시지 내 서버 버전 정보 제거 1. Apache 1) 조치 방법 “/etc/htpd/conf/httpd.conf” 파일 안에서 1. ServerTokens OS → ServerTokens Prod 2. ServerSignature On → ServerSignature Off 로 변경한 후 아파치를 재시작하면 헤더 값의 아파치 버전 정보 및 OS 정보를 제거할 수 있다. 2) 참고 URL http://zetawiki.com/wiki/CentOS_ 아파치_보안권장설정_ServerTokens_Prod,_ServerSignature_Off 2. IIS 1) 조치 방법 IIS 6.0 urlscan_setup 실행. 설치. \windows\system32\inetsrv\urlscan\urlscan.ini 파일을 열어 다음 수정(RemoveServerHeader=0 을 RemoveServerHeader=1 로 변경) 서비스에서 IIS Admin Service 재시작. IIS 7.0 IIS 관리자를 열고 관리하려는 수준으로 이동합니다. 기능 보기에서 HTTP 응답 헤더를 두 번 클릭합니다. HTTP 응답 헤더 페이지에서 제거할 헤더를 선택합니다. 작업 창에서 제거를 클릭하고 예를 클릭합니다. 2) 참고 URL IIS 6.0 : http://gonnie.tistory.com/entry/iis6- 응답헤더-감추기 IIS 7.0 : https://technet.microsoft.com/ko-kr/library/cc733102(v=ws.10).aspx 3. jetty 1) 조치 방법 “jetty.xml” 파일에서 jetty.send.server.version=false 설정 2) 참고 URL http://attenuated-perspicacity.blogspot.kr/2009/09/jetty-61x-hardening.html 4. Nginx ...

American Fuzzy Lop And Address Sanitizer

American Fuzzy Lop And Address Sanitizer Address Sanitizer(ASAN) 단지 gcc/clang에 -fsanitize=address 옵션을 추가하는 것으로 간단히 사용할 수 있지만 그 효과는 충분하다. Example Out Of Bounds Read #include <stdio.h> int main() { int a[ 2 ] = { 3 , 1 }; int i = 2 ; printf ( "%i\n" , a[i]); } 예제 파일을 OutOfBoundsRead.c 로 생성하고 ASAN 옵션을 지정하여 clang 으로 컴파일하자. clang -g -fsanitize = address -fno -omit -frame -pointer OutOfBoundsRead . c -o OutOfBoundsRead 생성된 OutBoundsRead 파일을 실행하면 다음과 같은 결과를 볼 수 있다. ================================================================= ==3678==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc0ba87428 at pc 0x47b7db bp 0x7ffc0ba87390 sp 0x7ffc0ba87388 READ of size 4 at 0x7ffc0ba87428 thread T0 ==3678==WARNING: Trying to symbolize code, but external symbolizer is not initialized! #0 0x47b7da (/root/ASAN/OutOfBoundsRead+0x47b7da) #1 0x7faba0260f44 (/lib/x86_64-linux-gnu/libc.so.6+0x21f44) #2 0x...