diff options
author | Darren Tucker <dtucker@cvs.openbsd.org> | 2018-04-13 03:57:27 +0000 |
---|---|---|
committer | Darren Tucker <dtucker@cvs.openbsd.org> | 2018-04-13 03:57:27 +0000 |
commit | 8adbf9e98a5b65138571bedc1198e1eca804ed21 (patch) | |
tree | b72459ef0606405bd4382033877fad1c79d25bcb /usr.bin/ssh | |
parent | 24fb6166b97816f05ec45586882e78a881b9ffbd (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.c | 43 | ||||
-rw-r--r-- | usr.bin/ssh/servconf.h | 3 | ||||
-rw-r--r-- | usr.bin/ssh/sshd.c | 41 |
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); |