Subject: serious security hole in wu-ftpd v2.4

From: David Greenman <[email-masked]>
Date: Thu, 02 Jan 1997 05:34:10 -0800

Summary
-------
   There is a serious security bug in wu-ftpd v2.4 (including the
version from Academ) which may allow both regular and anonymous users
to access files as uid 0 (root). The same bug is also responsible for
an advisory lock not being unlocked - potentially resulting in blocked
access to future ftp logins and filling up the process table and swap
space until the server dies.

Description
-----------
   The ftpd server installs two signal handlers as part of its startup
procedure: one to catch SIGPIPE for control/data port connection closes,
and one to catch SIGURG for when out-of-band signaling is used with the
ABOR (abort file transfer) command. The SIGPIPE handler is:

lostconn(int sig)
{
    if (debug)
        syslog(LOG_DEBUG, "lost connection to %s [%s]", remotehost, remoteaddr);
    dologout(-1);
}

   ...which causes the ftpd server to exit via dologout() whenever the
control or data connection is unexpectedly closed. The function
dologout() is:

dologout(int status)
{
    if (logged_in) {
        (void) seteuid((uid_t) 0);
        logwtmp(ttyline, "", "");
    }
    syslog(LOG_INFO, "FTP session closed");
    if (xferlog)
        close(xferlog);
    acl_remove();
    /* beware of flushing buffers after a SIGPIPE */
    _exit(status);
}

   ...which changes the effective uid to 0, adds a logout record to wtmp,
closes the xferlog log file, removes this instance of the server from
the PID file for his class, and exits.
   The initial part of the SIGURG handler is:

myoob(int sig)
{
    char *cp;

    /* only process if transfer occurring */
    if (!transflag)
        return;
    cp = tmpline;
    if (getline(cp, 7, stdin) == NULL) {
        reply(221, "You could at least say goodbye.");
        dologout(0);
    }
    upper(cp);
    if (strcmp(cp, "ABOR\r\n") == 0) {
        tmpline[0] = '\0';
        reply(426, "Transfer aborted. Data connection closed.");
        reply(226, "Abort successful");
        longjmp(urgcatch, 1);
    }
(...)

   ...which does nothing if transflag is 0 - not currently doing a file
transfer, but if you are and an ABOR command was issued along with the
"urgent" data that caused this signal, then the procedure does a longjmp()
restoring the "urgcatch" saved state, which ultimately returns back to the
server main command loop.
   Now, some FTP client programs will abort a file transfer by BOTH
closing the data connection AND issuing an ABOR with out-of-band signaling.
In many instances, the ftpd server gets the SIGPIPE due to the closed data
connection and begins the dologout() procedure. While it is uid 0 and
sometimes while it also has the pid file advisory lock (which occurs in
the acl_remove() procedure), the ftpd server will sometimes be interrupted
by the SIGURG that is delivered as part of the ABOR command. Since
transflag is not 0 (a file transfer WAS occuring), the signal handler does
a longjmp which ultimately returns to the main command loop...and presto,
you are uid 0, and to make things even better, the xferlog log file is
closed so nothing you do is even logged.

   A patch to fix this problem is simple:

*** ftpd.c.bak	Wed Jan  1 22:10:05 1997
--- ftpd.c	Wed Jan  1 22:10:14 1997
***************
*** 2503,2508 ****
--- 2503,2514 ----
  void
  dologout(int status)
  {
+     /*
+      * Prevent reception of SIGURG from resulting in a resumption
+      * back to the main program loop.
+      */
+     transflag = 0;
+ 
      if (logged_in) {
          (void) seteuid((uid_t) 0);
          logwtmp(ttyline, "", "");

   ...which does as the comment suggests.

-DG

David Greenman
Core-team/Principal Architect, The FreeBSD Project

---

From: Alain Magloire <[email-masked]>
Date: Fri, 3 Jan 1997 03:47:39 -0500 (EST)

> Summary

[Summary ...deleted]

> Description

[.. description deleted ..]

> you are uid 0, and to make things even better, the xferlog log file is

presto .. !! ;-)
I would not rejoice so fast.

The `bug' is not at all limited to the SIGPIPE.
It is everywhere ;-).

You can try to exploit it by sending randomly MSG_OOB.
If luck is on your side you may stumble in a sensitive area
and have root access.

I mean by sensitive area a time/place where wu-ftpd sets is uid to 0

oldid = geteuid();
...
seteuid((uid_t)0);
/* sensitive area */
...
...
..
seteuid((uid_t)oldid);
It's all over the code: when doing UPLOAD, PORT, at end_login, etc ..

Don't waste your time it's almost impossible to win
the race condition(But it is feasible).

What make it so attractive about the SIGPIPE is:
in that sensitive area the client stands a chance to
win the race condition ....

The culprit like you have demonstrated clearly in your post
is acl_remove();

acl_remove() is a real pain :-( because of the loop.
I would get unconfortable if I see in my syslogs

"sleeping: fcntl lock of pid file failed"

It means(to me) that I have a very busy FTP server
and the race condition can be win by a _very perseverent_ Cracker.

>    A patch to fix this problem is simple:
[... patch deleted ...]

The patch is fine ;-)
Ignoring the signals is also correct

############################
diff -c ftpd.c ftpd.c.orig
*** ftpd.c      Fri Jan  3 03:27:33 1997
--- ftpd.c.orig Thu Jan  2 19:56:50 1997
***************
*** 3025,3033 ****
  int status;
  #endif
  {
-   signal(SIGPIPE, SIG_IGN);
-   signal(SIGURG, SIG_IGN);
-
      if (logged_in) {
          (void) seteuid((uid_t) 0);
          logwtmp(ttyline, "", "");
--- 3025,3030 ----
############################

The ``real'' solution would be to block those
signals in the sensitive areas someting like

sigblock(sigmask(SIGURG));
/* now do the stuff ..*/
..
seteuid((uid_t)0);
...
...
seteuid((uid_t)oldid);

The ``ultimate'' solution would be to rewrite the
damn thing with security in mind ;-)

FWIW, I was able to reproduce the `bug' in many places
But .. I cheated ;-) after all those seteuid(0) I add:
sleep(100); 
and recompile the server.

for example in dologout();

dologout(int status)
{

    if (logged_in) {
        (void) seteuid((uid_t) 0);
        logwtmp(ttyline, "", "");
    }
[....]
sleep(100);            /* a litle extra to win*/
...
    _exit(status);
}

I was able to win the race condition everytime ;-)

-- 
Bonne anne'e, alain

---

From: David Greenman <[email-masked]>
Date: Fri, 03 Jan 1997 01:18:42 -0800

>If luck is on your side you may stumble in a sensitive area

   Actually not. Look more closely; "myoob" will only do anything when
transflag != 0. I've spent a significant amount of time verifying that
the user's uid is always in effect when transflag != 0, and the only
hole is the one I identified in dologout(). Also, getdatasock() will reset
the uid back to the user's...unfortunately, this is after a file (or tar)
has already been opened by root.
   ...but hey, if I missed a case somewhere, I've no problem with admitting
I'm wrong! :-)
   I agree, however, that the setjmp/longjmp stuff is poorly used. It would be
much better to isolate the routines that actually do the read/writes into
a seperate function so that more context is available after the longjmp().
In the current code, it is not possible to clean up malloced buffers, for
instance, after a user does an ABOR.

-DG

David Greenman