920d3a7
From 9286875a73b2de7736b5e50692739d314cd8d9dc Mon Sep 17 00:00:00 2001
920d3a7
From: Darren Tucker <dtucker@zip.com.au>
920d3a7
Date: Fri, 15 Jul 2016 13:32:45 +1000
920d3a7
Subject: [PATCH] Determine appropriate salt for invalid users.
920d3a7
920d3a7
When sshd is processing a non-PAM login for a non-existent user it uses
920d3a7
the string from the fakepw structure as the salt for crypt(3)ing the
920d3a7
password supplied by the client.  That string has a Blowfish prefix, so on
920d3a7
systems that don't understand that crypt will fail fast due to an invalid
920d3a7
salt, and even on those that do it may have significantly different timing
920d3a7
from the hash methods used for real accounts (eg sha512).  This allows
920d3a7
user enumeration by, eg, sending large password strings.  This was noted
920d3a7
by EddieEzra.Harari at verint.com (CVE-2016-6210).
920d3a7
920d3a7
To mitigate, use the same hash algorithm that root uses for hashing
920d3a7
passwords for users that do not exist on the system.  ok djm@
920d3a7
---
920d3a7
 auth-passwd.c           | 12 ++++++++----
920d3a7
 openbsd-compat/xcrypt.c | 34 ++++++++++++++++++++++++++++++++++
920d3a7
 2 files changed, 42 insertions(+), 4 deletions(-)
920d3a7
920d3a7
diff --git a/auth-passwd.c b/auth-passwd.c
920d3a7
index 63ccf3c..530b5d4 100644
920d3a7
--- a/auth-passwd.c
920d3a7
+++ b/auth-passwd.c
920d3a7
@@ -193,7 +193,7 @@ int
920d3a7
 sys_auth_passwd(Authctxt *authctxt, const char *password)
920d3a7
 {
920d3a7
 	struct passwd *pw = authctxt->pw;
920d3a7
-	char *encrypted_password;
920d3a7
+	char *encrypted_password, *salt = NULL;
920d3a7
 
920d3a7
 	/* Just use the supplied fake password if authctxt is invalid */
920d3a7
 	char *pw_password = authctxt->valid ? shadow_pw(pw) : pw->pw_passwd;
920d3a7
@@ -202,9 +202,13 @@ sys_auth_passwd(Authctxt *authctxt, const char *password)
920d3a7
 	if (strcmp(pw_password, "") == 0 && strcmp(password, "") == 0)
920d3a7
 		return (1);
920d3a7
 
920d3a7
-	/* Encrypt the candidate password using the proper salt. */
920d3a7
-	encrypted_password = xcrypt(password,
920d3a7
-	    (pw_password[0] && pw_password[1]) ? pw_password : "xx");
920d3a7
+	/*
920d3a7
+	 * Encrypt the candidate password using the proper salt, or pass a
920d3a7
+	 * NULL and let xcrypt pick one.
920d3a7
+	 */
920d3a7
+	if (authctxt->valid && pw_password[0] && pw_password[1])
920d3a7
+		salt = pw_password;
920d3a7
+	encrypted_password = xcrypt(password, salt);
920d3a7
 
920d3a7
 	/*
920d3a7
 	 * Authentication is accepted if the encrypted passwords
920d3a7
diff --git a/openbsd-compat/xcrypt.c b/openbsd-compat/xcrypt.c
920d3a7
index 8577cbd..8913bb8 100644
920d3a7
--- a/openbsd-compat/xcrypt.c
920d3a7
+++ b/openbsd-compat/xcrypt.c
920d3a7
@@ -25,6 +25,7 @@
920d3a7
 #include "includes.h"
920d3a7
 
920d3a7
 #include <sys/types.h>
920d3a7
+#include <string.h>
920d3a7
 #include <unistd.h>
920d3a7
 #include <pwd.h>
920d3a7
 
920d3a7
@@ -62,11 +63,44 @@
920d3a7
 #  define crypt DES_crypt
920d3a7
 # endif
920d3a7
 
920d3a7
+/*
920d3a7
+ * Pick an appropriate password encryption type and salt for the running
920d3a7
+ * system.
920d3a7
+ */
920d3a7
+static const char *
920d3a7
+pick_salt(void)
920d3a7
+{
920d3a7
+	struct passwd *pw;
920d3a7
+	char *passwd, *p;
920d3a7
+	size_t typelen;
920d3a7
+	static char salt[32];
920d3a7
+
920d3a7
+	if (salt[0] != '\0')
920d3a7
+		return salt;
920d3a7
+	strlcpy(salt, "xx", sizeof(salt));
920d3a7
+	if ((pw = getpwuid(0)) == NULL)
920d3a7
+		return salt;
920d3a7
+	passwd = shadow_pw(pw);
920d3a7
+	if (passwd[0] != '$' || (p = strrchr(passwd + 1, '$')) == NULL)
920d3a7
+		return salt;  /* no $, DES */
920d3a7
+	typelen = p - passwd + 1;
920d3a7
+	strlcpy(salt, passwd, MIN(typelen, sizeof(salt)));
920d3a7
+	explicit_bzero(passwd, strlen(passwd));
920d3a7
+	return salt;
920d3a7
+}
920d3a7
+
920d3a7
 char *
920d3a7
 xcrypt(const char *password, const char *salt)
920d3a7
 {
920d3a7
 	char *crypted;
920d3a7
 
920d3a7
+	/*
920d3a7
+	 * If we don't have a salt we are encrypting a fake password for
920d3a7
+	 * for timing purposes.  Pick an appropriate salt.
920d3a7
+	 */
920d3a7
+	if (salt == NULL)
920d3a7
+		salt = pick_salt();
920d3a7
+
920d3a7
 # ifdef HAVE_MD5_PASSWORDS
920d3a7
         if (is_md5_salt(salt))
920d3a7
                 crypted = md5_crypt(password, salt);
920d3a7
920d3a7
From 283b97ff33ea2c641161950849931bd578de6946 Mon Sep 17 00:00:00 2001
920d3a7
From: Darren Tucker <dtucker@zip.com.au>
920d3a7
Date: Fri, 15 Jul 2016 13:49:44 +1000
920d3a7
Subject: [PATCH] Mitigate timing of disallowed users PAM logins.
920d3a7
920d3a7
When sshd decides to not allow a login (eg PermitRootLogin=no) and
920d3a7
it's using PAM, it sends a fake password to PAM so that the timing for
920d3a7
the failure is not noticeably different whether or not the password
920d3a7
is correct.  This behaviour can be detected by sending a very long
920d3a7
password string which is slower to hash than the fake password.
920d3a7
920d3a7
Mitigate by constructing an invalid password that is the same length
920d3a7
as the one from the client and thus takes the same time to hash.
920d3a7
Diff from djm@
920d3a7
---
920d3a7
 auth-pam.c | 35 +++++++++++++++++++++++++++++++----
920d3a7
 1 file changed, 31 insertions(+), 4 deletions(-)
920d3a7
920d3a7
diff --git a/auth-pam.c b/auth-pam.c
920d3a7
index 451de78..465b5a7 100644
920d3a7
--- a/auth-pam.c
920d3a7
+++ b/auth-pam.c
920d3a7
@@ -232,7 +232,6 @@ static int sshpam_account_status = -1;
920d3a7
 static char **sshpam_env = NULL;
920d3a7
 static Authctxt *sshpam_authctxt = NULL;
920d3a7
 static const char *sshpam_password = NULL;
920d3a7
-static char badpw[] = "\b\n\r\177INCORRECT";
920d3a7
 
920d3a7
 /* Some PAM implementations don't implement this */
920d3a7
 #ifndef HAVE_PAM_GETENVLIST
920d3a7
@@ -795,12 +794,35 @@ sshpam_query(void *ctx, char **name, char **info,
920d3a7
 	return (-1);
920d3a7
 }
920d3a7
 
920d3a7
+/*
920d3a7
+ * Returns a junk password of identical length to that the user supplied.
920d3a7
+ * Used to mitigate timing attacks against crypt(3)/PAM stacks that
920d3a7
+ * vary processing time in proportion to password length.
920d3a7
+ */
920d3a7
+static char *
920d3a7
+fake_password(const char *wire_password)
920d3a7
+{
920d3a7
+	const char junk[] = "\b\n\r\177INCORRECT";
920d3a7
+	char *ret = NULL;
920d3a7
+	size_t i, l = wire_password != NULL ? strlen(wire_password) : 0;
920d3a7
+
920d3a7
+	if (l >= INT_MAX)
920d3a7
+		fatal("%s: password length too long: %zu", __func__, l);
920d3a7
+
920d3a7
+	ret = malloc(l + 1);
920d3a7
+	for (i = 0; i < l; i++)
920d3a7
+		ret[i] = junk[i % (sizeof(junk) - 1)];
920d3a7
+	ret[i] = '\0';
920d3a7
+	return ret;
920d3a7
+}
920d3a7
+
920d3a7
 /* XXX - see also comment in auth-chall.c:verify_response */
920d3a7
 static int
920d3a7
 sshpam_respond(void *ctx, u_int num, char **resp)
920d3a7
 {
920d3a7
 	Buffer buffer;
920d3a7
 	struct pam_ctxt *ctxt = ctx;
920d3a7
+	char *fake;
920d3a7
 
920d3a7
 	debug2("PAM: %s entering, %u responses", __func__, num);
920d3a7
 	switch (ctxt->pam_done) {
920d3a7
@@ -821,8 +843,11 @@ sshpam_respond(void *ctx, u_int num, char **resp)
920d3a7
 	    (sshpam_authctxt->pw->pw_uid != 0 ||
920d3a7
 	    options.permit_root_login == PERMIT_YES))
920d3a7
 		buffer_put_cstring(&buffer, *resp);
920d3a7
-	else
920d3a7
-		buffer_put_cstring(&buffer, badpw);
920d3a7
+	else {
920d3a7
+		fake = fake_password(*resp);
920d3a7
+		buffer_put_cstring(&buffer, fake);
920d3a7
+		free(fake);
920d3a7
+	}
920d3a7
 	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
920d3a7
 		buffer_free(&buffer);
920d3a7
 		return (-1);
920d3a7
@@ -1166,6 +1191,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
920d3a7
 {
920d3a7
 	int flags = (options.permit_empty_passwd == 0 ?
920d3a7
 	    PAM_DISALLOW_NULL_AUTHTOK : 0);
920d3a7
+	char *fake = NULL;
920d3a7
 
920d3a7
 	if (!options.use_pam || sshpam_handle == NULL)
920d3a7
 		fatal("PAM: %s called when PAM disabled or failed to "
920d3a7
@@ -1181,7 +1207,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
920d3a7
 	 */
920d3a7
 	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
920d3a7
 	    options.permit_root_login != PERMIT_YES))
920d3a7
-		sshpam_password = badpw;
920d3a7
+		sshpam_password = fake = fake_password(password);
920d3a7
 
920d3a7
 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
920d3a7
 	    (const void *)&passwd_conv);
920d3a7
@@ -1191,6 +1217,7 @@ sshpam_auth_passwd(Authctxt *authctxt, const char *password)
920d3a7
 
920d3a7
 	sshpam_err = pam_authenticate(sshpam_handle, flags);
920d3a7
 	sshpam_password = NULL;
920d3a7
+	free(fake);
920d3a7
 	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
920d3a7
 		debug("PAM: password authentication accepted for %.100s",
920d3a7
 		    authctxt->user);
920d3a7