Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 20 Jan 2003 14:10:54 -0800
From:      "Crist J. Clark" <crist.clark@attbi.com>
To:        security@freebsd.org, net@freebsd.org
Subject:   ftpd.c DoS Fix
Message-ID:  <20030120221054.GB34751@blossom.cjclark.org>

next in thread | raw e-mail | index | archive | help

--HcAYCG3uE/tztfnV
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

The current design of the FTP daemon leaves it open to denial of
service attacks where an attacker can lock out all other users from
making PORT (active) data connections. This DoS is mitigated by the
fact the attacker must have a valid login on the server (although
anonymous access will do) and that PASV (passive) mode is not
affected.

The problem lies in the way in which the server fails when it tries to
open a data connection in active mode. If the connection attempt fails
with an EADDRINUSE error, the server waits and tries the connection
again. Durning this wait period, 90 seconds is the hard-coded value,
the process is bound to port 20, using the bind() call. This is an
exclusive bind(). No other processes may bind() to port 20 for this 90
second wait. This locks all other processes from setting up active
data connections during this 90 second wait. Once the 90 seconds is
up, the attacker can easily start another 90 second wait.

The result is that an attacker with limited resources can prevent all
other users from making data connections rendering the server almost
useless.

I will describe an example of how to attack. It is trivial to automate
with a Perl script, but I will not be providing such a tool on a
public list.

1) Using a telnet client, log into the test victim FTP server
(obviously, this should be your server and it's availability should
not be critical).

2) Set up a data connection to your attacker host.

3) Set up a listening process on the attacker on the right port for
the data connection.

4) Do a LIST command.

5) Using the same port you used in (2), repeat (2), (3), and (4). (You
can't wait to long between (4) and (5) in this example, since we are
choking things up by trying to run over our previous connection still
in the TIME_WAIT state.)

That's it. You will have locked out all other data connections. During
the 90 seconds, try firing up another FTP session to the host and try
to do anything involving an active data connection (make sure you're
not using passive mode, in FreeBSD's ftp client, type 'pass').

I have a quick fix for this. Instead of holding onto our bind() of 20
while we wait, we release, and bind() again at our next try. The
inline patch below shows the diff without whitespace changes. A
complete diff is attached. The diffs are from HEAD, but it should
apply to any RELENG_* branch fine.

Unless anyone has some objections, I plan to commit this to HEAD and
RELENG_4 today and see about re@ and security-officer@ approval for
other branches.

As a final note, I came across this bug in a different vendor's FTP
daemon before checking if FreeBSD was vulnerable. You might want to
check you favorite FTP daemon today.

Index: ftpd.c
===================================================================
RCS file: /export/freebsd/ncvs/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.132
diff -u -b -r1.132 ftpd.c
--- ftpd.c	16 Jan 2003 14:25:32 -0000	1.132
+++ ftpd.c	20 Jan 2003 21:26:39 -0000
@@ -1772,7 +1772,7 @@
 {
 	char sizebuf[32];
 	FILE *file;
-	int retry = 0, tos;
+	int retry = 0, tos, conerrno;
 
 	file_size = size;
 	byte_count = 0;
@@ -1840,6 +1840,7 @@
 	if (usedefault)
 		data_dest = his_addr;
 	usedefault = 1;
+	do {
 	file = getdatasock(mode);
 	if (file == NULL) {
 		char hostbuf[BUFSIZ], portbuf[BUFSIZ];
@@ -1852,16 +1853,22 @@
 		return (NULL);
 	}
 	data = fileno(file);
-	while (connect(data, (struct sockaddr *)&data_dest,
-	    data_dest.su_len) < 0) {
-		if (errno == EADDRINUSE && retry < swaitmax) {
+		conerrno = 0;
+		if (connect(data, (struct sockaddr *)&data_dest,
+		    data_dest.su_len) == 0)
+			break;
+		conerrno = errno;
+		(void) fclose(file);
+		data = -1;
+		if (conerrno == EADDRINUSE) {
 			sleep((unsigned) swaitint);
 			retry += swaitint;
-			continue;
+		} else {
+			break;
 		}
+	} while (retry <= swaitmax);
+	if (conerrno != 0) {
 		perror_reply(425, "Can't build data connection");
-		(void) fclose(file);
-		data = -1;
 		return (NULL);
 	}
 	reply(150, "Opening %s mode data connection for '%s'%s.",

-- 
Crist J. Clark                     |     cjclark@alum.mit.edu
                                   |     cjclark@jhu.edu
http://people.freebsd.org/~cjc/    |     cjc@freebsd.org

--HcAYCG3uE/tztfnV
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ftpd.diff"

Index: ftpd.c
===================================================================
RCS file: /export/freebsd/ncvs/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.132
diff -u -r1.132 ftpd.c
--- ftpd.c	16 Jan 2003 14:25:32 -0000	1.132
+++ ftpd.c	20 Jan 2003 21:26:39 -0000
@@ -1772,7 +1772,7 @@
 {
 	char sizebuf[32];
 	FILE *file;
-	int retry = 0, tos;
+	int retry = 0, tos, conerrno;
 
 	file_size = size;
 	byte_count = 0;
@@ -1840,28 +1840,35 @@
 	if (usedefault)
 		data_dest = his_addr;
 	usedefault = 1;
-	file = getdatasock(mode);
-	if (file == NULL) {
-		char hostbuf[BUFSIZ], portbuf[BUFSIZ];
-		getnameinfo((struct sockaddr *)&data_source,
-			data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
-			portbuf, sizeof(portbuf),
-			NI_NUMERICHOST|NI_NUMERICSERV);
-		reply(425, "Can't create data socket (%s,%s): %s.",
-			hostbuf, portbuf, strerror(errno));
-		return (NULL);
-	}
-	data = fileno(file);
-	while (connect(data, (struct sockaddr *)&data_dest,
-	    data_dest.su_len) < 0) {
-		if (errno == EADDRINUSE && retry < swaitmax) {
+	do {
+		file = getdatasock(mode);
+		if (file == NULL) {
+			char hostbuf[BUFSIZ], portbuf[BUFSIZ];
+			getnameinfo((struct sockaddr *)&data_source,
+				data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
+				portbuf, sizeof(portbuf),
+				NI_NUMERICHOST|NI_NUMERICSERV);
+			reply(425, "Can't create data socket (%s,%s): %s.",
+				hostbuf, portbuf, strerror(errno));
+			return (NULL);
+		}
+		data = fileno(file);
+		conerrno = 0;
+		if (connect(data, (struct sockaddr *)&data_dest,
+		    data_dest.su_len) == 0)
+			break;
+		conerrno = errno;
+		(void) fclose(file);
+		data = -1;
+		if (conerrno == EADDRINUSE) {
 			sleep((unsigned) swaitint);
 			retry += swaitint;
-			continue;
+		} else {
+			break;
 		}
+	} while (retry <= swaitmax);
+	if (conerrno != 0) {
 		perror_reply(425, "Can't build data connection");
-		(void) fclose(file);
-		data = -1;
 		return (NULL);
 	}
 	reply(150, "Opening %s mode data connection for '%s'%s.",

--HcAYCG3uE/tztfnV--

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-net" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20030120221054.GB34751>