幽灵GHOST漏洞解析.docx

上传人:b****7 文档编号:9249205 上传时间:2023-02-03 格式:DOCX 页数:34 大小:459.39KB
下载 相关 举报
幽灵GHOST漏洞解析.docx_第1页
第1页 / 共34页
幽灵GHOST漏洞解析.docx_第2页
第2页 / 共34页
幽灵GHOST漏洞解析.docx_第3页
第3页 / 共34页
幽灵GHOST漏洞解析.docx_第4页
第4页 / 共34页
幽灵GHOST漏洞解析.docx_第5页
第5页 / 共34页
点击查看更多>>
下载资源
资源描述

幽灵GHOST漏洞解析.docx

《幽灵GHOST漏洞解析.docx》由会员分享,可在线阅读,更多相关《幽灵GHOST漏洞解析.docx(34页珍藏版)》请在冰豆网上搜索。

幽灵GHOST漏洞解析.docx

幽灵GHOST漏洞解析

CVE-2015-0235:

幽灵(GHOST)漏洞解析

0x01摘要

Qualys公司在进行内部代码审核时,发现了一个在GNUC库(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的模块。

-据悉,GNUC库的第一个易受攻击版本是glibc-2.2,发布于2000年11月10日,相当有年头了。

-据了解,是有一些方法可以减轻影响的。

事实上,这个漏洞其实在2013年5月21日就已经被修复了(在glibc-2.17和glibc-2.18的发行版之间)。

不幸的是,当时它并没有被认为是一个安全威胁。

其结果是,大多数稳定版和长期支持版本现在依然暴露在漏洞影响下,比如:

Debian7(wheezy),红帽企业版Linux6和7,CentOS6和7,Ubuntu12.04。

0x02分析

存在漏洞的函数__nss_hostname_digits_dots()由glibc的非重入版本的文件:

nss/getXXbyYY.c,以及重入版本:

nss/getXXbyYY_r.c提供。

然而,这个函数的调用是由#ifdefHANDLE_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的代码如下:

1.int 

2. __nss_hostname_digits_dots (const char *name, struct hostent *resbuf, 

3.                             char **buffer, size_t *buffer_size, 

4.                             size_t buflen, struct hostent **result, 

5.                            enum nss_status *status, int af, int *h_errnop) 

6. { 

7. 

8.   if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':

') 

9.     { 

10.      const char *cp; 

11.       char *hostname; 

12.       typedef unsigned char host_addr_t[16]; 

13.       host_addr_t *host_addr; 

14.       typedef char *host_addr_list_t[2]; 

15.       host_addr_list_t *h_addr_ptrs; 

16.       char **h_alias_ptr; 

17.       size_t size_needed; 

18. 

19.      size_needed = (sizeof (*host_addr) 

20.                     + sizeof (*h_addr_ptrs) + strlen (name) + 1); 

21. 

22.       if (buffer_size == NULL) 

23.        { 

24.          if (buflen < size_needed) 

25.            { 

26. 

27.               goto done; 

28.             } 

29.         } 

30.       else if (buffer_size !

= NULL && *buffer_size < size_needed) 

31.         { 

32.           char *new_buf; 

33.           *buffer_size = size_needed; 

34.           new_buf = (char *) realloc (*buffer, *buffer_size); 

35. 

36.         if (new_buf == NULL) 

37.             { 

38.               goto done; 

39.             } 

40.           *buffer = new_buf; 

41.         } 

42. 

43.       host_addr = (host_addr_t *) *buffer; 

44.       h_addr_ptrs = (host_addr_list_t *) 

45.         ((char *) host_addr + sizeof (*host_addr)); 

46.       h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs)); 

47.       hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr); 

48. 

49.       if (isdigit (name[0])) 

50.         { 

51.           for (cp = name;; ++cp) 

52.             { 

53.               if (*cp == '\0') 

54.                 { 

55.                   int ok; 

56. 

57.                  if (*--cp == '.') 

58.                     break; 

59. 

60.                  if (af == AF_INET) 

61.                     ok = __inet_aton (name, (struct in_addr *) host_addr); 

62.                  else 

63.                     { 

64.                       assert (af == AF_INET6); 

65.                       ok = inet_pton (af, name, host_addr) > 0; 

66.                     } 

67.                  if (!

 ok) 

68.                   { 

69. 

70.                       goto done; 

71.                     } 

72. 

73.                  resbuf->h_name = strcpy (hostname, name); 

74. 

75.                   goto done; 

76.                } 

77. 

78.               if (!

isdigit (*cp) && *cp !

= '.') 

79.                break; 

80.            } 

81.        } 

Ln85-86计算所需的缓冲区大小size_needed来存储三个不同的实体:

HOST_ADDR,h_addr_ptrs和name(hostname)。

Ln88-117确保缓冲区足够大:

Ln88-97对应于函数重入的情况,Ln98-117为非重入的情况。

Ln121-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()在Ln200,但是这里没有缓冲区溢出:

1.size_needed = (sizeof (*host_addr) 

2.                          + sizeof (*h_addr_ptrs) + strlen (name) + 1); 

3. 

4.           host_addr = (host_addr_t *) *buffer; 

5.           h_addr_ptrs = (host_addr_list_t *) 

6.             ((char *) host_addr + sizeof (*host_addr)); 

7.          hostname = (char *) h_addr_ptrs + sizeof (*h_addr_ptrs); 

8. 

9.                  resbuf->h_name = strcpy (hostname, name); 

为了在行157触发溢出,主机名参数必须符合下列要求:

-它的第一个字符必须是数字(Ln127)。

-它的最后一个字符不能是点“.”(Ln135)。

-它必须只包含数字和点(Ln197)(我们称之为“数字和点”的要求)。

-它必须足够长以溢出缓冲区。

例如,非重入的gethostbyname*()函数最开始就会通过调用malloc(1024)来分配自己的缓冲区(申请“1KB”)。

-地址必须成功地解析为IPv4地址。

该解析由INET_ATON()(Ln143)完成,或作为inet_ptonIPv6地址()(Ln147)

-经过仔细分析这两个函数,我们可以进一步完善这一“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的影响现在显著减少了,原因是:

◆补丁已经存在(因为2013年5月21日),并在2013年8月12日发布的glibc-2.18中被应用和测试:

[BZ#15014](更新日志)

*nss/getXXbyYY_r.c(INTERNAL(REENTRANT_NAME))[HANDLE_DIGITS_DOTS]:

当数字-点解析成功时设置any_service。

*nss/digits_dots.c(__nss_hostname_digits_dots):

为IPv6地址解析时,删除多余的变量声明和缓冲的重新分配。

可重入函数调用时,总是需要设置NSS状态。

当缓冲区太小时使用NETDB_INTERNAL而不是TRY_AGAIN。

正确计算了所需大小。

*nss/Makefile(tests):

加入test-digits-dots。

*nss/test-digits-dots.c:

新测试文件。

◆gethostbyname*()函数是过时的;随着IPv6的到来,新的应用程序应该使用getaddrinfo()来代替。

◆许多程序,特别是SUID文件可本地访问时,当且仅当之前调用inet_aton()失败时,会使用gethostbyname()。

但是,就算到这里了,后续其他调用也必须成功,这样才能走到溢出的地方(“inet-aton”规定):

但是这是不可能的,所以用这样的方案的程序是安全的。

◆大多数其他的程序,尤其是可远程访问的服务器,会使用gethostbyname()来执行反查DNS(FCrDNS,也被称为full-circlereverseDNS)。

这些程序通常是安全的,因为传递到的gethostbyname()的主机名通常都已经被DNS软件预先检查了:

(RFC1123)“每个label最多有63个8位数字,由点分隔,最多总计有255个八进制数字”。

这使得它不可能满足“1KB”要求。

事实上,glibc的的DNS解析器可以产生高达(最多)1025字符的主机名(如bit-string标签,特殊的或非打印的字符)。

但这会引入反斜杠('\'),这样也会使得它不可能满足“只有数字和点”的要求。

0x04案例分析

在本节中,我们将分析真实的调用gethostbyname*()函数的例子,但我们首先介绍一个小测试程序,检查系统是否脆弱:

1.[user@...ora-19 ~]$ cat > GHOST.c << EOF 

2.#include  

3.#include  

4.#include  

5.#include  

6.#include  

7.  

8.#define CANARY "in_the_coal_mine" 

9.  

10.struct { 

11.  char buffer[1024]; 

12.  char canary[sizeof(CANARY)]; 

13.} temp = { "buffer", CANARY }; 

14.  

15.int main(void) { 

16.  struct hostent resbuf; 

17.  struct hostent *result; 

18.  int herrno; 

19.  int retval; 

20.  

21.  /*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/ 

22.  size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1; 

23.  char name[sizeof(temp.buffer)]; 

24.  memset(name, '0', len); 

25.  name[len] = '\0'; 

26.  

27.  retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno); 

28.  

29.  if (strcmp(temp.canary, CANARY) !

= 0) { 

30.    puts("vulnerable"); 

31.    exit(EXIT_SUCCESS); 

32.  } 

33.  if (retval == ERANGE) { 

34.    puts("not vulnerable"); 

35.    exit(EXIT_SUCCESS); 

36.  } 

37.  puts("should not happen"); 

38.  exit(EXIT_FAILURE); 

39.} 

40.EOF 

41.  

42.[user@...ora-19 ~]$ gcc GHOST.c -o GHOST 

43.  

44.On Fedora 19 (glibc-2.17):

 

45.  

46.[user@...ora-19 ~]$ ./GHOST 

47.vulnerable 

48.  

49.On Fedora 20 (glibc-2.18):

 

50.  

51.[user@...ora-20 ~]$ ./GHOST 

52.not vulnerable 

4.1

glibc的本身包含了几个调用gethostbyname*()的函数。

特别是,仅当第一次调用inet_aton()失败时,getaddrinfo()会调用gethostbyname2_r():

按照“inet-aton”要求,这些内部调用是安全的。

例如,

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

1.at->family = AF_UNSPEC; 

2.  ... 

3.  if (__inet_aton (name, (struct in_addr *) at->addr) !

= 0) 

4.    { 

5.      if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET) 

6.        at->family = AF_INET; 

7.      else if (req->ai_family == AF_INET6 && (req->ai_flags & AI_V4MAPPED)) 

8.        { 

9.          ... 

10.          at->family = AF_INET6; 

11.        } 

12.      else 

13.        return -EAI_ADDRFAMILY; 

14.      ... 

15.    } 

16.  ... 

17.  if (at->family == AF_UNSPEC && (req->ai_flags & AI_NUMERICHOST) == 0) 

18.    { 

19.      ... 

20.          size_t tmpbuflen = 512; 

21.          char *tmpbuf = alloca (tmpbuflen); 

22.          ... 

23.              rc = __gethostbyname2_r (name, family, &th, tmpbuf, 

24.                                       tmpbuflen, &h, &herrno); 

25.      ... 

26.    } 

4.2-mount.nfs

类似的,mount.nfs也没有漏洞:

1.if (inet_aton(hostname, &addr->sin_addr)) 

2.           return 0; 

3.   if ((hp = gethostbyname(hostname)) == NULL) { 

4.           nfs_error(_("%s:

 can't get address for %s\n"), 

5.                           progname, hostname); 

6.           return -1; 

7.   } 

4.3-mtr

mtr也没有漏洞,因为它调用了getaddrinfo()而不是gethostbyname*()。

1.#ifdef ENABLE_IPV6 

2.  /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ 

3.  ... 

4.  error = getaddrinfo( Hostname, NULL, &hints, &res ); 

5.  if ( error ) { 

6.    if (error == EAI_SYSTEM) 

7.       perror ("Failed to resolve host"); 

8.    else 

9.       fprintf (stderr, "Fail

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 图表模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1