summaryrefslogtreecommitdiff
path: root/usr.bin/ssh
diff options
context:
space:
mode:
authorDarren Tucker <dtucker@cvs.openbsd.org>2018-04-13 03:57:27 +0000
committerDarren Tucker <dtucker@cvs.openbsd.org>2018-04-13 03:57:27 +0000
commit8adbf9e98a5b65138571bedc1198e1eca804ed21 (patch)
treeb72459ef0606405bd4382033877fad1c79d25bcb /usr.bin/ssh
parent24fb6166b97816f05ec45586882e78a881b9ffbd (diff)
Defend against user enumeration timing attacks.
This establishes a minimum time for each failed authentication attempt (5ms) and adds a per-user constant derived from a host secret (0-4ms). Based on work by joona.kannisto at tut.fi, ok markus@ djm@.
Diffstat (limited to 'usr.bin/ssh')
-rw-r--r--usr.bin/ssh/auth2.c43
-rw-r--r--usr.bin/ssh/servconf.h3
-rw-r--r--usr.bin/ssh/sshd.c41
3 files changed, 84 insertions, 3 deletions
diff --git a/usr.bin/ssh/auth2.c b/usr.bin/ssh/auth2.c
index da173d0450a..9230661016c 100644
--- a/usr.bin/ssh/auth2.c
+++ b/usr.bin/ssh/auth2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2.c,v 1.145 2018/03/03 03:15:51 djm Exp $ */
+/* $OpenBSD: auth2.c,v 1.146 2018/04/13 03:57:26 dtucker Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@@ -54,6 +54,7 @@
#endif
#include "monitor_wrap.h"
#include "ssherr.h"
+#include "digest.h"
/* import */
extern ServerOptions options;
@@ -200,6 +201,42 @@ input_service_request(int type, u_int32_t seq, struct ssh *ssh)
return 0;
}
+#define MIN_FAIL_DELAY_SECONDS 0.005
+static double
+user_specific_delay(const char *user)
+{
+ char b[512];
+ size_t len = ssh_digest_bytes(SSH_DIGEST_SHA512);
+ u_char *hash = xmalloc(len);
+ double delay;
+
+ (void)snprintf(b, sizeof b, "%llu%s", options.timing_secret, user);
+ if (ssh_digest_memory(SSH_DIGEST_SHA512, b, strlen(b), hash, len) != 0)
+ fatal("%s: ssh_digest_memory", __func__);
+ /* 0-4.2 ms of delay */
+ delay = (double)PEEK_U32(hash) / 1000 / 1000 / 1000 / 1000;
+ freezero(hash, len);
+ debug3("%s: user specific delay %0.3lfms", __func__, delay/1000);
+ return MIN_FAIL_DELAY_SECONDS + delay;
+}
+
+static void
+ensure_minimum_time_since(double start, double seconds)
+{
+ struct timespec ts;
+ double elapsed = monotime_double() - start, req = seconds, remain;
+
+ /* if we've already passed the requested time, scale up */
+ while ((remain = seconds - elapsed) < 0.0)
+ seconds *= 2;
+
+ ts.tv_sec = remain;
+ ts.tv_nsec = (remain - ts.tv_sec) * 1000000000;
+ debug3("%s: elapsed %0.3lfms, delaying %0.3lfms (requested %0.3lfms)",
+ __func__, elapsed*1000, remain*1000, req*1000);
+ nanosleep(&ts, NULL);
+}
+
/*ARGSUSED*/
static int
input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
@@ -208,6 +245,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
Authmethod *m = NULL;
char *user, *service, *method, *style = NULL;
int authenticated = 0;
+ double tstart = monotime_double();
if (authctxt == NULL)
fatal("input_userauth_request: no authctxt");
@@ -269,6 +307,9 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
debug2("input_userauth_request: try method %s", method);
authenticated = m->userauth(ssh);
}
+ if (!authctxt->authenticated)
+ ensure_minimum_time_since(tstart,
+ user_specific_delay(authctxt->user));
userauth_finish(ssh, authenticated, method, NULL);
free(service);
diff --git a/usr.bin/ssh/servconf.h b/usr.bin/ssh/servconf.h
index 9da7a203cc8..71ac8332526 100644
--- a/usr.bin/ssh/servconf.h
+++ b/usr.bin/ssh/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.130 2017/10/25 00:19:47 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.131 2018/04/13 03:57:26 dtucker Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -207,6 +207,7 @@ typedef struct {
int fingerprint_hash;
int expose_userauth_info;
+ u_int64_t timing_secret;
} ServerOptions;
/* Information about the incoming connection as used by Match */
diff --git a/usr.bin/ssh/sshd.c b/usr.bin/ssh/sshd.c
index 3d658a9b059..e0b894eed61 100644
--- a/usr.bin/ssh/sshd.c
+++ b/usr.bin/ssh/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.507 2018/04/10 00:10:49 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.508 2018/04/13 03:57:26 dtucker Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1323,6 +1323,43 @@ set_process_rdomain(struct ssh *ssh, const char *name)
debug("%s: set routing domain %d (was %d)", __func__, rtable, ortable);
}
+static void
+accumulate_host_timing_secret(struct sshbuf *server_cfg,
+ const struct sshkey *key)
+{
+ static struct ssh_digest_ctx *ctx;
+ u_char *hash;
+ size_t len;
+ struct sshbuf *buf;
+ int r;
+
+ if (ctx == NULL && (ctx = ssh_digest_start(SSH_DIGEST_SHA512)) == NULL)
+ fatal("%s: ssh_digest_start", __func__);
+ if (key == NULL) { /* finalize */
+ /* add server config in case we are using agent for host keys */
+ if (ssh_digest_update(ctx, sshbuf_ptr(server_cfg),
+ sshbuf_len(server_cfg)) != 0)
+ fatal("%s: ssh_digest_update", __func__);
+ len = ssh_digest_bytes(SSH_DIGEST_SHA512);
+ hash = xmalloc(len);
+ if (ssh_digest_final(ctx, hash, len) != 0)
+ fatal("%s: ssh_digest_final", __func__);
+ options.timing_secret = PEEK_U64(hash);
+ freezero(hash, len);
+ ssh_digest_free(ctx);
+ ctx = NULL;
+ return;
+ }
+ if ((buf = sshbuf_new()) == NULL)
+ fatal("%s could not allocate buffer", __func__);
+ if ((r = sshkey_private_serialize(key, buf)) != 0)
+ fatal("sshkey_private_serialize: %s", ssh_err(r));
+ if (ssh_digest_update(ctx, sshbuf_ptr(buf), sshbuf_len(buf)) != 0)
+ fatal("%s: ssh_digest_update", __func__);
+ sshbuf_reset(buf);
+ sshbuf_free(buf);
+}
+
/*
* Main program for the daemon.
*/
@@ -1597,6 +1634,7 @@ main(int ac, char **av)
keytype = pubkey->type;
} else if (key != NULL) {
keytype = key->type;
+ accumulate_host_timing_secret(&cfg, key);
} else {
error("Could not load host key: %s",
options.host_key_files[i]);
@@ -1622,6 +1660,7 @@ main(int ac, char **av)
key ? "private" : "agent", i, sshkey_ssh_name(pubkey), fp);
free(fp);
}
+ accumulate_host_timing_secret(&cfg, NULL);
if (!sensitive_data.have_ssh2_key) {
logit("sshd: no hostkeys available -- exiting.");
exit(1);