bin/59777: ftpd(8)/FreeBSD 5: potential username enumeration vulnerability; PAM "template" user can't be used

Nick Leuta skynick at mail.sc.ru
Fri Nov 28 15:40:33 PST 2003


>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 at 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:


More information about the freebsd-bugs mailing list