2015年1月28日 星期三

GHOST : The Vulnerability of Glibc

前言

偶然間在網路上看到了一個名為 Ghost 的漏洞,所以稍微整理一下,以方便自己理解,背景知識為敘述漏洞時可能需要知道的地方,由於網路上文件頗多,這裡僅挑一些 link 作為參考,便不在此多談。

背景知識

受影響版本

  • glibc 2.2 - 2.17

漏洞概述

簡單來說在 glibc 中的 __nss_hostname_digits_dots() 這個 function 中存在著 buffer overflow 的問題,而會 call 這個 function 的則是 gethostbyname*() 系列的 function , 這系列 funtion 主要是用來做 IP 和 hostname 的轉換也就是所謂的 DNS Query ,但是當給的 hostname 已經本身就是 IP 的型態呢?為了這種情況 glibc 便在 __nss_hostname_digits_dots() 加了判斷只要是 IP 的型態,便會跳過 DNS 查詢,然而這個地方卻發生了 buffer overflow 的問題,但 payload 必須符合下列條件

  • 第一個字元必須是數字
  • 最後一個不能為 dot (.)
  • 全部都必須是數字跟點
  • 需要夠大,足以 overflow

然而既然不能塞任意的 shellcode ,卻為什麼還是非常危險呢?這裡可能必須先知道 free chunk 是什麼?簡單來說是 glibc 中 malloc 管理分配記憶體的一個機制,會將目前為 free 的空間分成許多 chunk 並用 linked-list 串起來,只要 malloc 一個空間,就會從這個 linked-list 拿出來,主要是為了不讓分配的空間支離破碎,而這紀錄這些資訊的地方就是在每個 chunk 中最前面的地方,存有這個 chunk 是不是 inused 或是這個 chunk 大小多大等等資訊,更詳細的內容可參考這篇,或者是自行 google。

回到主題,為什麼這樣的問題會如此危險?原因是 overflow 之後攻擊者可以利用它去更改 heap 中,鄰近的 free chunk 的 header,以增加 free chunk 大小,要是後面又接著程式已使用的空間,那麼當系統在 allocate 給 user 這段空間時,便能任意更改程式所存在記憶體中的資訊,也有機會造成 memory leak 的問題,而且可以繞過 (ASLR,NX) 等保護。

漏洞細節

漏洞發生的 function 為 __nss_hostname_digits_dots() 主要在 hostnameIP 時就會使用到這個 function,而這個 function 的 code 中

85      size_needed = (sizeof (*host_addr)
86             + sizeof (*h_addr_ptrs) + strlen (name) + 1);

size_needed 會先去計算儲存 host_addr, h_addr_ptrs, 和 name (the hostname) 這三個空間的大小,接下來的 code 主要是確認預先分配的 buffer 是否夠大,要是不夠大則會再分配更大的空間給他,不過分成 reentrantnon-reentrant 兩種 case ,這裡小提一下什麼 reentrant 跟 non-reentrant 的差別,差別在於主要是可是重複執行及不可重複執行的程式碼,像是遞迴的 code 都是使用 local variable 的,所以基本上重複執行沒問題,但如果 code 使用到的是 static variable 並且是在 multi thread 的情況下,很有可能就會有 Race condition 的情況發生,詳細的就不在這裡多說了。

reentrant

 88   if (buffer_size == NULL)
 89     {
 90       if (buflen < size_needed)
 91         {
 92           if (h_errnop != NULL)
 93         *h_errnop = TRY_AGAIN;
 94           __set_errno (ERANGE);
 95           goto done;
 96         }
 97     }

non-reentrant

 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         {
106           save = errno;
107           free (*buffer);
108           *buffer = NULL;
109           *buffer_size = 0;
110           __set_errno (save);
111           if (h_errnop != NULL)
112         *h_errnop = TRY_AGAIN;
113           *result = NULL;
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);

從上面可以看到 buffer 的部分一共有四個不同的 pointer 指向他的的記憶體區塊,分別是 host_addr, h_addr_ptrs, h_alias_ptr, 和 hostname,但回頭看看剛剛 size_needed 那部份的 code 卻沒有計算到 sizeof (*h_alias_ptr) 的部分,因此我們可以 overflow 一個 char pointer 的大小,說到這裡可能又頭昏了,只好用圖來說明:

  • 下圖是說明 buffer 這個空間是透過 gethostbyname*() 去分配到的 heap 中的記憶體區塊,並且有四個 pointer 分別指到其中的一部分

  • 然而 size_needed 卻少計了 sizeof (*h_alias_ptr) ,讓系統以為所分配的大小夠塞全部的東西

但接下來這段 code 就是造成 overflow 的地方,主要是將 name 的內容塞到 hostname 所指的記憶體區塊

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

問題來了,假設 size_needed 剛好為預先分配的最大值 1024 ,那麼這時候 hostname 所指的位置應該只剩 sizeof(name)-sizeof(*h_alias_ptrs) 的空間可以放而已,因此當 name 複製到 hostname 時就會造成 overflow,就如下圖所示

這就是這次漏洞的主要問題點,不過要走到這項流程必須符合之前所說的條件才會達成,更詳細的內容請參考 Qualys 所釋出的漏洞分析,上面有更詳細的說明及測試。

POC

可測試系統中是否為安全的,使用方式為,請將下列程式碼寫入 ghost.c 檔案中,然後執行

$ gcc ghost.c -o ghost
$ ./ghost
# 如果出現 vulnerable 那麼就代表系統中存在這個風險
#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);
}

受引響的服務

Exim、clockdiff、pppd 等調用 gethostbyname*() 的服務 新增 wordpress 等 php 相關的 CMS 也有機會受影響

排除名單

apache, cups, dovecot, gnupg, isc-dhcp, lighttpd, mariadb/mysql, nfs-utils, nginx, nodejs, openldap, openssh, postfix, proftpd, pure-ftpd, rsyslog, samba, sendmail, sysklogd, syslog-ng, tcp_wrappers, vsftpd, xinetd. 雖然上述是目前應該不會有問題的,但還是建議更新一下 glibc 以防萬一

同場加映

這幾天下來又有研究人員發現以 php 所寫的相關應用也都有機會受到影響,像是 wordpress 等等 CMS ,攻擊者有機會藉由惡意的 domain 來觸發 ghost 這個漏洞,危害極大,所以老話一句,無論如何都請更新 Glibc ,以確保伺服器的安全。

修補方式

Debian 系列

# apt-get clean 
# apt-get update
# apt-get upgrade
# reboot

Red Hat 系列

# yum clean
# yum update
# yum install glibc*
# reboot

其他各版本的修補方式可參考這篇

Reference

Qualys Security Advisory CVE-2015-0235 glibc Cross Reference Freebuf threatpost

附註

如上述內容有任何錯誤的地方歡迎留言告知

沒有留言:

張貼留言