前言
偶然間在網路上看到了一個名為 Ghost 的漏洞,所以稍微整理一下,以方便自己理解,背景知識為敘述漏洞時可能需要知道的地方,由於網路上文件頗多,這裡僅挑一些 link 作為參考,便不在此多談。
背景知識
- vulnerability
- protection
- glibc
受影響版本
- 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()
主要在 hostname 為 IP 時就會使用到這個 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 是否夠大,要是不夠大則會再分配更大的空間給他,不過分成 reentrant 跟 non-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
附註
如上述內容有任何錯誤的地方歡迎留言告知