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>