From owner-freebsd-bugs@FreeBSD.ORG Fri Nov 28 15:40:33 2003 Return-Path: Delivered-To: freebsd-bugs@hub.freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id E011216A4CF for ; Fri, 28 Nov 2003 15:40:32 -0800 (PST) Received: from freefall.freebsd.org (freefall.freebsd.org [216.136.204.21]) by mx1.FreeBSD.org (Postfix) with ESMTP id 0D09C43FE3 for ; Fri, 28 Nov 2003 15:40:15 -0800 (PST) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.12.9/8.12.9) with ESMTP id hASNeFFY051156 for ; Fri, 28 Nov 2003 15:40:15 -0800 (PST) (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.12.9/8.12.9/Submit) id hASNeFBG051155; Fri, 28 Nov 2003 15:40:15 -0800 (PST) (envelope-from gnats) Resent-Date: Fri, 28 Nov 2003 15:40:15 -0800 (PST) Resent-Message-Id: <200311282340.hASNeFBG051155@freefall.freebsd.org> Resent-From: FreeBSD-gnats-submit@FreeBSD.org (GNATS Filer) Resent-To: freebsd-bugs@FreeBSD.org Resent-Reply-To: FreeBSD-gnats-submit@FreeBSD.org, Nick Leuta Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id A318D16A4CE for ; Fri, 28 Nov 2003 15:35:19 -0800 (PST) Received: from falcon.lipetsk.ru (falcon.lipetsk.ru [195.34.224.68]) by mx1.FreeBSD.org (Postfix) with ESMTP id EC2F243FDD for ; Fri, 28 Nov 2003 15:35:16 -0800 (PST) (envelope-from skynick@stu.lipetsk.ru) Received: from lstu by falcon.lipetsk.ru with UUCP id ; Sat, 29 Nov 2003 02:35:05 +0300 Received: from chuck2.lstu (chuck2.lstu [192.168.15.7]) by maverick.stu.int (8.9.3/8.8.5) with ESMTP id CAA23124 +0300 (MSK) Received: by chuck2.lstu (Postfix, from userid 1000) id C03BE49A29; Sat, 29 Nov 2003 02:28:14 +0300 (MSK) Message-Id: <20031128232814.C03BE49A29@chuck2.lstu> Date: Sat, 29 Nov 2003 02:28:14 +0300 (MSK) From: Nick Leuta To: FreeBSD-gnats-submit@FreeBSD.org X-Send-Pr-Version: 3.113 Subject: bin/59777: ftpd(8)/FreeBSD 5: potential username enumeration vulnerability; PAM "template" user can't be used X-BeenThere: freebsd-bugs@freebsd.org X-Mailman-Version: 2.1.1 Precedence: list Reply-To: Nick Leuta List-Id: Bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 28 Nov 2003 23:40:33 -0000 >Number: 59777 >Category: bin >Synopsis: ftpd(8)/FreeBSD 5: potential username enumeration vulnerability; PAM "template" user can't be used >Confidential: no >Severity: serious >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Fri Nov 28 15:40:14 PST 2003 >Closed-Date: >Last-Modified: >Originator: Nick Leuta >Release: FreeBSD 4.9-RC i386 >Organization: Lipetsk State Technical University >Environment: System: FreeBSD skynick.stu.lipetsk.ru 4.9-RC FreeBSD 4.9-RC #0: Sun Nov 23 19:53:55 MSK 2003 root@skynick.stu.lipetsk.ru:/usr/src/sys/compile/CORSAIR i386 >Description: There are two problems linked to each other... 1. "...A vulnerability has been identified in [ftpd(8)] allowing malicious people to enumerate valid usernames..." (these words were said about exactly the same situation with another BSD-based FTP server) The problem is that if a valid username and invalid password is supplied, a delay in the response may be done with "help" of any PAM module, whereas a response is returned immidiately if an invalid username and any password is supplied. Also, if the account exists, but hasn't a valid shell from /etc/shells, the corresponding reply will be returned without asking for password at all, and no time metering is required in this case (see also http://www.freebsd.org/cgi/query-pr.cgi?pr=misc/34171). Note 1: /etc/ftpusers leads to the similar effect, but i think that it isn't a security breach, because sending of the cleartext password of the "root" user leads to more security compromise than the enumeration of existence of the account with the name "root"... Note 2: PAM modules from FreeBSD's base system don't do the delay in the described situation, but a third-party module, for example, from ports collection, may to do it. 2. ftpd.c, auth_pam(): "With PAM we support the concept of a "template" user. The user enters a login name which is authenticated by PAM, usually via a remote service such as RADIUS or TACACS+. ..."template" name is used for setting the credentials, shell, and home directory. The name the user enters need only exist on the remote authentication server, but the template name must be present in the local password database." But ftpd(8) checks the existence of an account with the name entered by the user before the authentication itself, and if it doesn't exist, the authentication will be failed without an attempt to use PAM or another authentication methods. >How-To-Repeat: >Fix: diff -urN ftpd.ORI/ftpd.8 ftpd/ftpd.8 --- ftpd.ORI/ftpd.8 Sun Sep 14 20:42:46 2003 +++ ftpd/ftpd.8 Thu Nov 27 02:34:02 2003 @@ -330,8 +330,13 @@ .Pp .Bl -enum -offset indent .It -The login name must be in the password data base -and not have a null password. +The login name must be in the user database and not have a null password +(the exception is possible if PAM modules from the authentication management +group are used to set up a template user account; see below). +If a client is connected via TLS/SSL and the X.509 certificate-based +authentication is sufficient, it will be used instead of the password-based +one. Otherwise the standard authentication will be used. +.Pp In this case a password must be provided by the client before any file operations may be performed. If the user has an S/Key key, the response from a successful USER @@ -344,22 +349,32 @@ .Xr key 1 for more information on S/Key authentication. S/Key is a Trademark of Bellcore. +.Pp +If +.Xr pam 8 +is used for the authentication, PAM modules from the authentication management +group may set up some user account as the template. This user account will be +used in all routines for whose the user account (the user record) in the system +user database is mentioned, so an FTP user will have access privileges of +this system user account. .It The login name must not appear in the file -.Pa /etc/ftpusers . +.Pa /etc/ftpusers , +otherwise the login attempt will be refused without asking for a password. .It -The login name must not be a member of a group specified in the file -.Pa /etc/ftpusers . +The user account name must not be a member of a group specified in the file +.Pa /etc/ftpusers , +otherwise the login attempt will be refused without asking for a password. Entries in this file interpreted as group names are prefixed by an "at" .Ql \&@ sign. .It -The user must have a standard shell returned by +The user account must have a standard shell returned by .Xr getusershell 3 . .It -If the user name appears in the file +If the user account name appears in the file .Pa /etc/ftpchroot , -or the user is a member of a group with a group entry in this file, +or the user account is a member of a group with a group entry in this file, i.e. one prefixed with .Ql \&@ , the session's root will be changed to the directory specified @@ -385,9 +400,8 @@ .Dq anonymous or .Dq ftp , -an -anonymous ftp account must be present in the password -file (user +an anonymous ftp account must be present in the user +database (user .Dq ftp ) . In this case the user is allowed to log in by specifying any password (by convention an email address for diff -urN ftpd.ORI/ftpd.c ftpd/ftpd.c --- ftpd.ORI/ftpd.c Sat Nov 15 14:08:26 2003 +++ ftpd/ftpd.c Thu Nov 27 03:15:26 2003 @@ -966,15 +966,12 @@ * need to reset state. If name is "ftp" or "anonymous", the name is not in * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. * If account doesn't exist, ask for passwd anyway. Otherwise, check user - * requesting login privileges. Disallow anyone who does not have a standard - * shell as returned by getusershell(). Disallow anyone mentioned in the file + * requesting login privileges. Disallow anyone mentioned in the file * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. */ void user(char *name) { - char *cp, *shell; - if (logged_in) { if (guest) { reply(530, "Can't change user from guest login."); @@ -1012,26 +1009,30 @@ return; } - if ((pw = sgetpwnam(name))) { - if ((shell = pw->pw_shell) == NULL || *shell == 0) - shell = _PATH_BSHELL; - while ((cp = getusershell()) != NULL) - if (strcmp(cp, shell) == 0) - break; - endusershell(); + pw = sgetpwnam(name); + if (checkuser(_PATH_FTPUSERS, name, pw != NULL ? 1:0, NULL)) { + reply(530, "User %s access denied.", name); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s, %s (name is in %s)", + remotehost, name, _PATH_FTPUSERS); + pw = (struct passwd *) NULL; + return; + } + strncpy(curname, name, sizeof(curname)-1); - if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1, NULL)) { - reply(530, "User %s access denied.", name); - if (logging) - syslog(LOG_NOTICE, - "FTP LOGIN REFUSED FROM %s, %s", - remotehost, name); - pw = (struct passwd *) NULL; - return; - } + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) { + sigset_t oset, nset; + + (void) sigfillset(&nset); + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + sleep((unsigned) login_attempts); + (void) sigprocmask(SIG_SETMASK, &oset, NULL); } - if (logging) - strncpy(curname, name, sizeof(curname)-1); pwok = 0; #ifdef USE_PAM @@ -1048,12 +1049,6 @@ reply(331, "Password required for %s.", name); } askpasswd = 1; - /* - * Delay before reading passwd after first failed - * attempt to slow down passwd-guessing programs. - */ - if (login_attempts) - sleep((unsigned) login_attempts); } /* @@ -1238,8 +1233,7 @@ static int auth_pam(struct passwd **ppw, const char *pass) { - pam_handle_t *pamh = NULL; - const char *tmpl_user; + char *tmpl_user; const void *item; int rval; int e; @@ -1281,9 +1275,9 @@ */ if ((e = pam_get_item(pamh, PAM_USER, &item)) == PAM_SUCCESS) { - tmpl_user = (const char *) item; + tmpl_user = (char *) item; if (strcmp((*ppw)->pw_name, tmpl_user) != 0) - *ppw = getpwnam(tmpl_user); + *ppw = sgetpwnam(tmpl_user); } else syslog(LOG_ERR, "Couldn't get PAM_USER: %s", pam_strerror(pamh, e)); @@ -1347,17 +1341,64 @@ } askpasswd = 0; if (!guest) { /* "ftp" is only account allowed no password */ +#ifdef USE_PAM + /* PAM authentication + */ if (pw == NULL) { - rval = 1; /* failure below */ - goto skip; + /* Try to use a "template" user account */ + struct passwd tpw, *ppw = &tpw; + + tpw.pw_name = strdup(curname); + if (tpw.pw_name == NULL) + fatalerror("Ran out of memory."); + + rval = auth_pam(&ppw, passwd); + + /* If the initial value of the user name was changed + * by PAM, try to use the new name as the "template" + * user account */ + if (rval == 0) { + if ((ppw == NULL) || + (strcmp(ppw->pw_name, curname) == 0)) { + /* The "template" account doesn't exist + * or it wasn't passed back by PAM */ + rval = 1; + } else { + /* Use the "template" account */ + pw = ppw; + } + } + free(tpw.pw_name); + } else { + /* Normal PAM auth */ + rval = auth_pam(&pw, passwd); } -#ifdef USE_PAM - rval = auth_pam(&pw, passwd); + +#if 0 /* uncomment next lines if PR "reset syslog facility" is accepted */ + /* Reset the logging facility because it may be changed by PAM + * modules */ + ftpd_openlog(); +#endif + + if ((pw != NULL) && logging) { + if (strcmp(pw->pw_name, curname) != 0) + syslog(LOG_NOTICE, + "PAM template user account is: %s", + pw->pw_name); + } + if (rval >= 0) { opieunlock(); goto skip; } -#endif +#endif /* USE_PAM */ + /* Traditional UNIX authentication + */ + if (pw == NULL) { + rval = 1; /* failure below */ + goto skip; + } + if (opieverify(&opiedata, passwd) == 0) xpasswd = pw->pw_passwd; else if (pwok) { @@ -1381,11 +1422,11 @@ reply(530, "Login incorrect."); if (logging) { syslog(LOG_NOTICE, - "FTP LOGIN FAILED FROM %s", - remotehost); + "FTP LOGIN FAILED FROM %s (attempt: %d)", + remotehost, login_attempts+1); syslog(LOG_AUTHPRIV | LOG_NOTICE, - "FTP LOGIN FAILED FROM %s, %s", - remotehost, curname); + "FTP LOGIN FAILED FROM %s, %s (attempt: %d)", + remotehost, curname, login_attempts+1); } pw = NULL; if (login_attempts++ >= 5) { @@ -1395,6 +1436,30 @@ exit(0); } return; + } else { + /* + * Disallow anyone who does not have a standard shell + * as returned by getusershell(). This check is + * performed in the last step of the authentication to + * prevent the username enumeration. + */ + char *cp, *shell; + if ((shell = pw->pw_shell) == NULL || *shell == 0) + shell = _PATH_BSHELL; + while ((cp = getusershell()) != NULL) + if (strcmp(cp, shell) == 0) + break; + endusershell(); + + if (cp == NULL) { + reply(530, "User %s access denied.", curname); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s, %s (shell is not in /etc/shells)", + remotehost, curname); + /* SKYNICK: "return" is also possible */ + exit(0); + } } } login_attempts = 0; /* this time successful */ @@ -1415,7 +1480,7 @@ if (!auth_hostok(lc, remotehost, remote_ip)) { syslog(LOG_INFO|LOG_AUTH, "FTP LOGIN FAILED (HOST) as %s: permission denied.", - pw->pw_name); + curname); reply(530, "Permission denied.\n"); pw = NULL; return; @@ -1446,7 +1511,7 @@ /* open wtmp before chroot */ if (dowtmp) - ftpd_logwtmp(ttyline, pw->pw_name, + ftpd_logwtmp(ttyline, curname, (struct sockaddr *)&his_addr); logged_in = 1; @@ -1584,18 +1649,18 @@ } else { if (dochroot) reply(230, "User %s logged in, " - "access restrictions apply.", pw->pw_name); + "access restrictions apply.", curname); else - reply(230, "User %s logged in.", pw->pw_name); + reply(230, "User %s logged in.", curname); #ifdef SETPROCTITLE snprintf(proctitle, sizeof(proctitle), - "%s: user/%s", remotehost, pw->pw_name); + "%s: user/%s", remotehost, curname); setproctitle("%s", proctitle); #endif /* SETPROCTITLE */ if (logging) syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", - remotehost, pw->pw_name); + remotehost, curname); } #ifdef LOGIN_CAP login_close(lc); @@ -2248,7 +2313,7 @@ if (guest) printf(" Logged in anonymously\r\n"); else - printf(" Logged in as %s\r\n", pw->pw_name); + printf(" Logged in as %s\r\n", curname); } else if (askpasswd) printf(" Waiting for password\r\n"); else >Release-Note: >Audit-Trail: >Unformatted: