OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
 
From: Crist J. Clark (crist.clark_at_attbi.com)
Date: Mon Jan 20 2003 - 16:10:54 CST

  • Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

    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                     |     cjclarkalum.mit.edu
                                       |     cjclarkjhu.edu
    http://people.freebsd.org/~cjc/    |     cjcfreebsd.org
    


    To Unsubscribe: send mail to majordomoFreeBSD.org with "unsubscribe freebsd-security" in the body of the message