1.漏洞简介
代码审计公司Qualys的研究人员在glibc库中的__nss_hostname_digits_dots()函数中发现了一个缓冲区溢出的漏洞,这个bug可以经过gethostbyname*()函数被本地或者远程的触发。应用程序主要使用gethostbyname*()函数发起DNS请求,这个函数会将主机名称转换为ip地址。
2.POC验证
测试漏洞的POC来自http://www.openwall.com/lists/oss-security/2015/01/27/9的第4节,主要代码如下,原理是通过调用gethostbyname_r函数,关键参数为temp和name。拿64位系统举例,如果经过gethostbyname_r的调用刚好能够覆盖temp.buffer的1024个字节,那么temp.canary就不会变,则glibc库没有该漏洞,但是如果temp.canary的前8字节被覆盖为0则说明漏洞存在:
#define CANARY "in_the_coal_mine"
struct {
char buffer[1024];
char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };
int main(void) {
... ...
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);
}
... ...
}
POC运行成功:
3.漏洞原理与修复分析
漏洞原理
有漏洞的函数是nss/digits_dots.c中的__nss_hostname_digits_dots函数,而这个函数只有在nss/getXXbyYY.c和nss/getXXbyYY_r.c中被用到,且仅当定义了HANDLE_DIGITS_DOTS宏时才会被调用,这个宏则是定义在inet目录中的gethstbynm系列文件中,因此仅当调用gethostbyname*系列函数时会被调用:
#ifdef HANDLE_DIGITS_DOTS
switch (__nss_hostname_digits_dots (name, resbuf, &buffer, NULL,
buflen, result, &status, AF_VAL,
H_ERRNO_VAR_P))
{
case -1:
return errno;
case 1:
goto done;
}
#endif
下面来进入__nss_hostname_digits_dots函数分析:
int
__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
char **buffer, size_t *buffer_size,
size_t buflen, struct hostent **result,
enum nss_status *status, int af, int *h_errnop)
{
... ...
if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
{
const char *cp;
char *hostname;
typedef unsigned char host_addr_t[16];
host_addr_t *host_addr;
typedef char *host_addr_list_t[2];
host_addr_list_t *h_addr_ptrs;
char **h_alias_ptr;
size_t size_needed;
int addr_size;
... ...
size_needed = (sizeof (*host_addr)
+ sizeof (*h_addr_ptrs) + strlen (name) + 1);
if (buffer_size == NULL)
{
if (buflen < size_needed)
{
if (h_errnop != NULL)
*h_errnop = TRY_AGAIN;
__set_errno (ERANGE);
goto done;
}
}
else if (buffer_size != NULL && *buffer_size < size_needed)
{
char *new_buf;
*buffer_size = size_needed;
new_buf = (char *) realloc (*buffer, *buffer_size);
if (new_buf == NULL)
{
... ...
goto done;
}
*buffer = new_buf;
}
memset (*buffer, '\0', size_needed);
host_addr = (host_addr_t *) *buffer;
h_addr_ptrs = (host_addr_list_t *)
((char *) host_addr + sizeof (*host_addr));
h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
if (isdigit (name[0]))
{
for (cp = name;; ++cp)
{
if (*cp == '\0')
{
int ok;
if (*--cp == '.')
break;
if (af == AF_INET)
ok = __inet_aton (name, (struct in_addr *) host_addr);
else
{
assert (af == AF_INET6);
ok = inet_pton (af, name, host_addr) > 0;
}
if (! ok)
{
*h_errnop = HOST_NOT_FOUND;
if (buffer_size)
*result = NULL;
goto done;
}
}
... ...
resbuf->h_name = strcpy (hostname, name);
h_alias_ptr[0] = NULL;
resbuf->h_aliases = h_alias_ptr;
(*h_addr_ptrs)[0] = (char *) host_addr;