backdoor by authorized_keys2 leftovers

Damien Miller djm at mindrot.org
Mon May 16 13:14:15 EST 2011


On Mon, 16 May 2011, Damien Miller wrote:

> On Sun, 15 May 2011, Daniel Kahn Gillmor wrote:
> 
> > It seems somewhat unclear how AuthorizedKeysFile interacts with a Match
> > clause.
> > 
> > If the following makes an array of two authorizedkeysfiles:
> > 
> >  AuthorizedKeysFile foo
> >  AuthorizedKeysFile bar
> 
> So the question is whether to allow multiple directives that add to the
> list (as is the case in the slightly-broken patch I sent out yesterday)
> or to allow a single directive that specifies all the files on one line.
> 
> The latter is more clear for Match, but long lines are more likely to wrap
> and are harder to read in sshd_config.
> 
> That being said, there is plenty of room for the common cases that I can
> think of:
> 
> AuthorizedKeysFile .ssh/authorized_keys
> AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
> AuthorizedKeysFile /etc/ssh/authorized_keys/keys_%u .ssh/authorized_keys
> 
> So maybe all-keys-on-one-line is better.

Here's a diff that implements this:

Index: auth-rsa.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth-rsa.c,v
retrieving revision 1.79
diff -u -p -r1.79 auth-rsa.c
--- auth-rsa.c	3 Dec 2010 23:55:27 -0000	1.79
+++ auth-rsa.c	16 May 2011 03:12:58 -0000
@@ -157,44 +157,27 @@ auth_rsa_challenge_dialog(Key *key)
 	return (success);
 }
 
-/*
- * check if there's user key matching client_n,
- * return key if login is allowed, NULL otherwise
- */
-
-int
-auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey)
+static int
+rsa_key_allowed_in_file(struct passwd *pw, char *file,
+    const BIGNUM *client_n, Key **rkey)
 {
-	char line[SSH_MAX_PUBKEY_BYTES], *file;
+	char line[SSH_MAX_PUBKEY_BYTES];
 	int allowed = 0;
 	u_int bits;
 	FILE *f;
 	u_long linenum = 0;
 	Key *key;
 
-	/* Temporarily use the user's uid. */
-	temporarily_use_uid(pw);
-
-	/* The authorized keys. */
-	file = authorized_keys_file(pw);
 	debug("trying public RSA key file %s", file);
-	f = auth_openkeyfile(file, pw, options.strict_modes);
-	if (!f) {
-		xfree(file);
-		restore_uid();
-		return (0);
-	}
-
-	/* Flag indicating whether the key is allowed. */
-	allowed = 0;
-
-	key = key_new(KEY_RSA1);
+	if ((f = auth_openkeyfile(file, pw, options.strict_modes)) == NULL)
+		return 0;
 
 	/*
 	 * Go though the accepted keys, looking for the current key.  If
 	 * found, perform a challenge-response dialog to verify that the
 	 * user really has the corresponding private key.
 	 */
+	key = key_new(KEY_RSA1);
 	while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
 		char *cp;
 		char *key_options;
@@ -232,7 +215,10 @@ auth_rsa_key_allowed(struct passwd *pw, 
 		}
 		/* cp now points to the comment part. */
 
-		/* Check if the we have found the desired key (identified by its modulus). */
+		/*
+		 * Check if the we have found the desired key (identified
+		 * by its modulus).
+		 */
 		if (BN_cmp(key->rsa->n, client_n) != 0)
 			continue;
 
@@ -261,11 +247,7 @@ auth_rsa_key_allowed(struct passwd *pw, 
 		break;
 	}
 
-	/* Restore the privileged uid. */
-	restore_uid();
-
 	/* Close the file. */
-	xfree(file);
 	fclose(f);
 
 	/* return key if allowed */
@@ -273,7 +255,33 @@ auth_rsa_key_allowed(struct passwd *pw, 
 		*rkey = key;
 	else
 		key_free(key);
-	return (allowed);
+
+	return allowed;
+}
+
+/*
+ * check if there's user key matching client_n,
+ * return key if login is allowed, NULL otherwise
+ */
+
+int
+auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey)
+{
+	char *file;
+	u_int i, allowed = 0;
+
+	temporarily_use_uid(pw);
+
+	for (i = 0; !allowed && i < options.num_authkeys_files; i++) {
+		file = expand_authorized_keys(
+		    options.authorized_keys_files[i], pw);
+		allowed = rsa_key_allowed_in_file(pw, file, client_n, rkey);
+		xfree(file);
+	}
+
+	restore_uid();
+
+	return allowed;
 }
 
 /*
Index: auth.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth.c,v
retrieving revision 1.92
diff -u -p -r1.92 auth.c
--- auth.c	11 May 2011 04:47:06 -0000	1.92
+++ auth.c	16 May 2011 03:12:58 -0000
@@ -241,7 +241,7 @@ auth_root_allowed(char *method)
  *
  * This returns a buffer allocated by xmalloc.
  */
-static char *
+char *
 expand_authorized_keys(const char *filename, struct passwd *pw)
 {
 	char *file, ret[MAXPATHLEN];
@@ -262,12 +262,6 @@ expand_authorized_keys(const char *filen
 		fatal("expand_authorized_keys: path too long");
 	xfree(file);
 	return (xstrdup(ret));
-}
-
-char *
-authorized_keys_file(struct passwd *pw)
-{
-	return expand_authorized_keys(options.authorized_keys_file, pw);
 }
 
 char *
Index: auth.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth.h,v
retrieving revision 1.68
diff -u -p -r1.68 auth.h
--- auth.h	11 May 2011 04:47:06 -0000	1.68
+++ auth.h	16 May 2011 03:12:58 -0000
@@ -145,7 +145,7 @@ struct passwd * getpwnamallow(const char
 char	*get_challenge(Authctxt *);
 int	verify_response(Authctxt *, const char *);
 
-char	*authorized_keys_file(struct passwd *);
+char	*expand_authorized_keys(const char *, struct passwd *pw);
 char	*authorized_principals_file(struct passwd *);
 
 FILE	*auth_openkeyfile(const char *, struct passwd *, int);
Index: auth2-pubkey.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/auth2-pubkey.c,v
retrieving revision 1.28
diff -u -p -r1.28 auth2-pubkey.c
--- auth2-pubkey.c	11 May 2011 04:47:06 -0000	1.28
+++ auth2-pubkey.c	16 May 2011 03:12:58 -0000
@@ -435,7 +435,7 @@ user_cert_trusted_ca(struct passwd *pw, 
 int
 user_key_allowed(struct passwd *pw, Key *key)
 {
-	int success;
+	u_int success, i;
 	char *file;
 
 	if (auth_key_is_revoked(key))
@@ -447,9 +447,12 @@ user_key_allowed(struct passwd *pw, Key 
 	if (success)
 		return success;
 
-	file = authorized_keys_file(pw);
-	success = user_key_allowed2(pw, key, file);
-	xfree(file);
+	for (i = 0; !success && i < options.num_authkeys_files; i++) {
+		file = expand_authorized_keys(
+		    options.authorized_keys_files[i], pw);
+		success = user_key_allowed2(pw, key, file);
+		xfree(file);
+	}
 
 	return success;
 }
Index: pathnames.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/pathnames.h,v
retrieving revision 1.21
diff -u -p -r1.21 pathnames.h
--- pathnames.h	11 May 2011 04:47:06 -0000	1.21
+++ pathnames.h	16 May 2011 03:12:58 -0000
@@ -88,6 +88,9 @@
  */
 #define _PATH_SSH_USER_PERMITTED_KEYS	".ssh/authorized_keys"
 
+/* backward compat for protocol v2 */
+#define _PATH_SSH_USER_PERMITTED_KEYS2	".ssh/authorized_keys2"
+
 /*
  * Per-user and system-wide ssh "rc" files.  These files are executed with
  * /bin/sh before starting the shell or command if they exist.  They will be
Index: servconf.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/servconf.c,v
retrieving revision 1.215
diff -u -p -r1.215 servconf.c
--- servconf.c	11 May 2011 04:47:06 -0000	1.215
+++ servconf.c	16 May 2011 03:12:58 -0000
@@ -119,7 +119,7 @@ initialize_server_options(ServerOptions 
 	options->use_dns = -1;
 	options->client_alive_interval = -1;
 	options->client_alive_count_max = -1;
-	options->authorized_keys_file = NULL;
+	options->num_authkeys_files = 0;
 	options->num_accept_env = 0;
 	options->permit_tun = -1;
 	options->num_permitted_opens = -1;
@@ -249,8 +249,12 @@ fill_default_server_options(ServerOption
 		options->client_alive_interval = 0;
 	if (options->client_alive_count_max == -1)
 		options->client_alive_count_max = 3;
-	if (options->authorized_keys_file == NULL)
-		options->authorized_keys_file = xstrdup(_PATH_SSH_USER_PERMITTED_KEYS);
+	if (options->num_authkeys_files == 0) {
+		options->authorized_keys_files[options->num_authkeys_files++] =
+		    xstrdup(_PATH_SSH_USER_PERMITTED_KEYS);
+		options->authorized_keys_files[options->num_authkeys_files++] =
+		    xstrdup(_PATH_SSH_USER_PERMITTED_KEYS2);
+	}
 	if (options->permit_tun == -1)
 		options->permit_tun = SSH_TUNMODE_NO;
 	if (options->zero_knowledge_password_authentication == -1)
@@ -286,7 +290,7 @@ typedef enum {
 	sMaxStartups, sMaxAuthTries, sMaxSessions,
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
-	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
+	sClientAliveCountMax, sAuthorizedKeysFile,
 	sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel,
 	sMatch, sPermitOpen, sForceCommand, sChrootDirectory,
 	sUsePrivilegeSeparation, sAllowAgentForwarding,
@@ -391,7 +395,6 @@ static struct {
 	{ "clientaliveinterval", sClientAliveInterval, SSHCFG_GLOBAL },
 	{ "clientalivecountmax", sClientAliveCountMax, SSHCFG_GLOBAL },
 	{ "authorizedkeysfile", sAuthorizedKeysFile, SSHCFG_ALL },
-	{ "authorizedkeysfile2", sAuthorizedKeysFile2, SSHCFG_ALL },
 	{ "useprivilegeseparation", sUsePrivilegeSeparation, SSHCFG_GLOBAL},
 	{ "acceptenv", sAcceptEnv, SSHCFG_GLOBAL },
 	{ "permittunnel", sPermitTunnel, SSHCFG_ALL },
@@ -1197,11 +1200,22 @@ process_server_config_line(ServerOptions
 	 * AuthorizedKeysFile	/etc/ssh_keys/%u
 	 */
 	case sAuthorizedKeysFile:
-		charptr = &options->authorized_keys_file;
-		goto parse_tilde_filename;
+		if (*activep && options->num_authkeys_files == 0) {
+			while ((arg = strdelim(&cp)) && *arg != '\0') {
+				if (options->num_authkeys_files >=
+				    MAX_AUTHKEYS_FILES)
+					fatal("%s line %d: "
+					    "too many authorized keys files.",
+					    filename, linenum);
+				options->authorized_keys_files[
+				    options->num_authkeys_files++] =
+				    tilde_expand_filename(arg, getuid());
+			}
+		}
+		return 0;
+
 	case sAuthorizedPrincipalsFile:
 		charptr = &options->authorized_principals_file;
- parse_tilde_filename:
 		arg = strdelim(&cp);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: missing file name.",
@@ -1420,6 +1434,12 @@ parse_server_match_config(ServerOptions 
 		dst->n = src->n; \
 	} \
 } while(0)
+#define M_CP_STRARRAYOPT(n, num_n) do {\
+	if (src->num_n != 0) { \
+		for (dst->num_n = 0; dst->num_n < src->num_n; dst->num_n++) \
+			dst->n[dst->num_n] = xstrdup(src->n[dst->num_n]); \
+	} \
+} while(0)
 
 /*
  * Copy any supported values that are set.
@@ -1455,19 +1475,23 @@ copy_set_server_options(ServerOptions *d
 	M_CP_INTOPT(ip_qos_interactive);
 	M_CP_INTOPT(ip_qos_bulk);
 
+	M_CP_STROPT(authorized_principals_file);
 	M_CP_STROPT(banner);
+	M_CP_STROPT(revoked_keys_file);
+	M_CP_STROPT(trusted_user_ca_keys);
+
+	M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files);
+
 	if (preauth)
 		return;
+
 	M_CP_STROPT(adm_forced_command);
 	M_CP_STROPT(chroot_directory);
-	M_CP_STROPT(trusted_user_ca_keys);
-	M_CP_STROPT(revoked_keys_file);
-	M_CP_STROPT(authorized_keys_file);
-	M_CP_STROPT(authorized_principals_file);
 }
 
 #undef M_CP_INTOPT
 #undef M_CP_STROPT
+#undef M_CP_STRARRAYOPT
 
 void
 parse_server_config(ServerOptions *options, const char *filename, Buffer *conf,
@@ -1674,7 +1698,6 @@ dump_config(ServerOptions *o)
 	dump_cfg_string(sCiphers, o->ciphers);
 	dump_cfg_string(sMacs, o->macs);
 	dump_cfg_string(sBanner, o->banner);
-	dump_cfg_string(sAuthorizedKeysFile, o->authorized_keys_file);
 	dump_cfg_string(sForceCommand, o->adm_forced_command);
 	dump_cfg_string(sChrootDirectory, o->chroot_directory);
 	dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys);
@@ -1687,6 +1710,8 @@ dump_config(ServerOptions *o)
 	dump_cfg_string(sLogFacility, log_facility_name(o->log_facility));
 
 	/* string array arguments */
+	dump_cfg_strarray(sAuthorizedKeysFile, o->num_authkeys_files,
+	    o->authorized_keys_files);
 	dump_cfg_strarray(sHostKeyFile, o->num_host_key_files,
 	     o->host_key_files);
 	dump_cfg_strarray(sHostKeyFile, o->num_host_cert_files,
Index: servconf.h
===================================================================
RCS file: /cvs/src/usr.bin/ssh/servconf.h,v
retrieving revision 1.96
diff -u -p -r1.96 servconf.h
--- servconf.h	11 May 2011 04:47:06 -0000	1.96
+++ servconf.h	16 May 2011 03:12:58 -0000
@@ -27,6 +27,7 @@
 #define MAX_HOSTCERTS		256	/* Max # host certificates. */
 #define MAX_ACCEPT_ENV		256	/* Max # of env vars. */
 #define MAX_MATCH_GROUPS	256	/* Max # of groups for Match. */
+#define MAX_AUTHKEYS_FILES	256	/* Max # of authorized_keys files. */
 
 /* permit_root_login */
 #define	PERMIT_NOT_SET		-1
@@ -145,7 +146,8 @@ typedef struct {
 					 * disconnect the session
 					 */
 
-	char   *authorized_keys_file;	/* File containing public keys */
+	u_int num_authkeys_files;	/* Files containing public keys */
+	char   *authorized_keys_files[MAX_AUTHKEYS_FILES];
 
 	char   *adm_forced_command;
 
Index: sshd.8
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshd.8,v
retrieving revision 1.260
diff -u -p -r1.260 sshd.8
--- sshd.8	28 Oct 2010 18:33:28 -0000	1.260
+++ sshd.8	16 May 2011 03:12:59 -0000
@@ -435,10 +435,12 @@ is run, and if that
 does not exist either, xauth is used to add the cookie.
 .Sh AUTHORIZED_KEYS FILE FORMAT
 .Cm AuthorizedKeysFile
-specifies the file containing public keys for
+specifies the file or files containing public keys for
 public key authentication;
-if none is specified, the default is
-.Pa ~/.ssh/authorized_keys .
+if none is specified, the default is both
+.Pa ~/.ssh/authorized_keys
+and
+.Pa ~/.ssh/authorized_keys2 .
 Each line of the file contains one
 key (empty lines and lines starting with a
 .Ql #
Index: sshd_config.5
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sshd_config.5,v
retrieving revision 1.131
diff -u -p -r1.131 sshd_config.5
--- sshd_config.5	8 Dec 2010 04:02:47 -0000	1.131
+++ sshd_config.5	16 May 2011 03:12:59 -0000
@@ -168,8 +168,14 @@ After expansion,
 .Cm AuthorizedKeysFile
 is taken to be an absolute path or one relative to the user's home
 directory.
-The default is
-.Dq .ssh/authorized_keys .
+The default is both
+.Dq .ssh/authorized_keys
+and
+.Dq .ssh/authorized_keys2 .
+Multiple files may be listed, either on a single line separated by
+whitespace or on additional
+.Cm AuthorizedKeysFile
+lines.
 .It Cm AuthorizedPrincipalsFile
 Specifies a file that lists principal names that are accepted for
 certificate authentication.


More information about the openssh-unix-dev mailing list