-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
upstream: Add a facility to sshd(8) to penalise particular
problematic client behaviours, controlled by two new sshd_config(5) options: PerSourcePenalties and PerSourcePenaltyExemptList. When PerSourcePenalties are enabled, sshd(8) will monitor the exit status of its child pre-auth session processes. Through the exit status, it can observe situations where the session did not authenticate as expected. These conditions include when the client repeatedly attempted authentication unsucessfully (possibly indicating an attack against one or more accounts, e.g. password guessing), or when client behaviour caused sshd to crash (possibly indicating attempts to exploit sshd). When such a condition is observed, sshd will record a penalty of some duration (e.g. 30 seconds) against the client's address. If this time is above a minimum threshold specified by the PerSourcePenalties, then connections from the client address will be refused (along with any others in the same PerSourceNetBlockSize CIDR range). Repeated offenses by the same client address will accrue greater penalties, up to a configurable maximum. A PerSourcePenaltyExemptList option allows certain address ranges to be exempt from all penalties. We hope these options will make it significantly more difficult for attackers to find accounts with weak/guessable passwords or exploit bugs in sshd(8) itself. PerSourcePenalties is off by default, but we expect to enable it automatically in the near future. much feedback markus@ and others, ok markus@ OpenBSD-Commit-ID: 89ded70eccb2b4926ef0366a4d58a693de366cca
- Loading branch information
Showing
12 changed files
with
982 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: misc.h,v 1.108 2024/05/17 00:30:24 djm Exp $ */ | ||
/* $OpenBSD: misc.h,v 1.109 2024/06/06 17:15:25 djm Exp $ */ | ||
|
||
/* | ||
* Author: Tatu Ylonen <[email protected]> | ||
|
@@ -252,6 +252,7 @@ void notify_complete(struct notifier_ctx *, const char *, ...) | |
|
||
typedef void (*sshsig_t)(int); | ||
sshsig_t ssh_signal(int, sshsig_t); | ||
int signal_is_crash(int); | ||
|
||
/* On OpenBSD time_t is int64_t which is long long. */ | ||
/* #define SSH_TIME_T_MAX LLONG_MAX */ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: monitor.c,v 1.239 2024/05/17 06:42:04 jsg Exp $ */ | ||
/* $OpenBSD: monitor.c,v 1.240 2024/06/06 17:15:25 djm Exp $ */ | ||
/* | ||
* Copyright 2002 Niels Provos <[email protected]> | ||
* Copyright 2002 Markus Friedl <[email protected]> | ||
|
@@ -161,6 +161,7 @@ static char *auth_submethod = NULL; | |
static u_int session_id2_len = 0; | ||
static u_char *session_id2 = NULL; | ||
static pid_t monitor_child_pid; | ||
int auth_attempted = 0; | ||
|
||
struct mon_table { | ||
enum monitor_reqtype type; | ||
|
@@ -296,6 +297,10 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) | |
authenticated = (monitor_read(ssh, pmonitor, | ||
mon_dispatch, &ent) == 1); | ||
|
||
/* Record that auth was attempted to set exit status later */ | ||
if ((ent->flags & MON_AUTH) != 0) | ||
auth_attempted = 1; | ||
|
||
/* Special handling for multiple required authentications */ | ||
if (options.num_auth_methods != 0) { | ||
if (authenticated && | ||
|
@@ -353,6 +358,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) | |
fatal_f("authentication method name unknown"); | ||
|
||
debug_f("user %s authenticated by privileged process", authctxt->user); | ||
auth_attempted = 0; | ||
ssh->authctxt = NULL; | ||
ssh_packet_set_log_preamble(ssh, "user %s", authctxt->user); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: monitor_wrap.c,v 1.130 2024/05/17 00:30:24 djm Exp $ */ | ||
/* $OpenBSD: monitor_wrap.c,v 1.131 2024/06/06 17:15:25 djm Exp $ */ | ||
/* | ||
* Copyright 2002 Niels Provos <[email protected]> | ||
* Copyright 2002 Markus Friedl <[email protected]> | ||
|
@@ -29,6 +29,7 @@ | |
|
||
#include <sys/types.h> | ||
#include <sys/uio.h> | ||
#include <sys/wait.h> | ||
|
||
#include <errno.h> | ||
#include <pwd.h> | ||
|
@@ -73,6 +74,7 @@ | |
#include "session.h" | ||
#include "servconf.h" | ||
#include "monitor_wrap.h" | ||
#include "srclimit.h" | ||
|
||
#include "ssherr.h" | ||
|
||
|
@@ -137,6 +139,36 @@ mm_request_send(int sock, enum monitor_reqtype type, struct sshbuf *m) | |
fatal_f("write: %s", strerror(errno)); | ||
} | ||
|
||
static void | ||
mm_reap(void) | ||
{ | ||
int status = -1; | ||
|
||
if (!mm_is_monitor()) | ||
return; | ||
while (waitpid(pmonitor->m_pid, &status, 0) == -1) { | ||
if (errno == EINTR) | ||
continue; | ||
pmonitor->m_pid = -1; | ||
fatal_f("waitpid: %s", strerror(errno)); | ||
} | ||
if (WIFEXITED(status)) { | ||
if (WEXITSTATUS(status) != 0) { | ||
debug_f("preauth child exited with status %d", | ||
WEXITSTATUS(status)); | ||
cleanup_exit(255); | ||
} | ||
} else if (WIFSIGNALED(status)) { | ||
error_f("preauth child terminated by signal %d", | ||
WTERMSIG(status)); | ||
cleanup_exit(signal_is_crash(WTERMSIG(status)) ? | ||
EXIT_CHILD_CRASH : 255); | ||
} else { | ||
error_f("preauth child terminated abnormally"); | ||
cleanup_exit(EXIT_CHILD_CRASH); | ||
} | ||
} | ||
|
||
void | ||
mm_request_receive(int sock, struct sshbuf *m) | ||
{ | ||
|
@@ -149,6 +181,7 @@ mm_request_receive(int sock, struct sshbuf *m) | |
if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) { | ||
if (errno == EPIPE) { | ||
debug3_f("monitor fd closed"); | ||
mm_reap(); | ||
cleanup_exit(255); | ||
} | ||
fatal_f("read: %s", strerror(errno)); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: servconf.c,v 1.407 2024/05/17 01:17:40 djm Exp $ */ | ||
/* $OpenBSD: servconf.c,v 1.408 2024/06/06 17:15:25 djm Exp $ */ | ||
/* | ||
* Copyright (c) 1995 Tatu Ylonen <[email protected]>, Espoo, Finland | ||
* All rights reserved | ||
|
@@ -163,6 +163,16 @@ initialize_server_options(ServerOptions *options) | |
options->per_source_max_startups = -1; | ||
options->per_source_masklen_ipv4 = -1; | ||
options->per_source_masklen_ipv6 = -1; | ||
options->per_source_penalty_exempt = NULL; | ||
options->per_source_penalty.enabled = -1; | ||
options->per_source_penalty.max_sources = -1; | ||
options->per_source_penalty.overflow_mode = -1; | ||
options->per_source_penalty.penalty_crash = -1; | ||
options->per_source_penalty.penalty_authfail = -1; | ||
options->per_source_penalty.penalty_noauth = -1; | ||
options->per_source_penalty.penalty_grace = -1; | ||
options->per_source_penalty.penalty_max = -1; | ||
options->per_source_penalty.penalty_min = -1; | ||
options->max_authtries = -1; | ||
options->max_sessions = -1; | ||
options->banner = NULL; | ||
|
@@ -402,6 +412,24 @@ fill_default_server_options(ServerOptions *options) | |
options->per_source_masklen_ipv4 = 32; | ||
if (options->per_source_masklen_ipv6 == -1) | ||
options->per_source_masklen_ipv6 = 128; | ||
if (options->per_source_penalty.enabled == -1) | ||
options->per_source_penalty.enabled = 0; | ||
if (options->per_source_penalty.max_sources == -1) | ||
options->per_source_penalty.max_sources = 65536; | ||
if (options->per_source_penalty.overflow_mode == -1) | ||
options->per_source_penalty.overflow_mode = PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE; | ||
if (options->per_source_penalty.penalty_crash == -1) | ||
options->per_source_penalty.penalty_crash = 90; | ||
if (options->per_source_penalty.penalty_grace == -1) | ||
options->per_source_penalty.penalty_grace = 20; | ||
if (options->per_source_penalty.penalty_authfail == -1) | ||
options->per_source_penalty.penalty_authfail = 5; | ||
if (options->per_source_penalty.penalty_noauth == -1) | ||
options->per_source_penalty.penalty_noauth = 1; | ||
if (options->per_source_penalty.penalty_min == -1) | ||
options->per_source_penalty.penalty_min = 15; | ||
if (options->per_source_penalty.penalty_max == -1) | ||
options->per_source_penalty.penalty_max = 600; | ||
if (options->max_authtries == -1) | ||
options->max_authtries = DEFAULT_AUTH_FAIL_MAX; | ||
if (options->max_sessions == -1) | ||
|
@@ -479,6 +507,7 @@ fill_default_server_options(ServerOptions *options) | |
CLEAR_ON_NONE(options->chroot_directory); | ||
CLEAR_ON_NONE(options->routing_domain); | ||
CLEAR_ON_NONE(options->host_key_agent); | ||
CLEAR_ON_NONE(options->per_source_penalty_exempt); | ||
|
||
for (i = 0; i < options->num_host_key_files; i++) | ||
CLEAR_ON_NONE(options->host_key_files[i]); | ||
|
@@ -513,6 +542,7 @@ typedef enum { | |
sBanner, sUseDNS, sHostbasedAuthentication, | ||
sHostbasedUsesNameFromPacketOnly, sHostbasedAcceptedAlgorithms, | ||
sHostKeyAlgorithms, sPerSourceMaxStartups, sPerSourceNetBlockSize, | ||
sPerSourcePenalties, sPerSourcePenaltyExemptList, | ||
sClientAliveInterval, sClientAliveCountMax, sAuthorizedKeysFile, | ||
sGssAuthentication, sGssCleanupCreds, sGssStrictAcceptor, | ||
sAcceptEnv, sSetEnv, sPermitTunnel, | ||
|
@@ -645,6 +675,8 @@ static struct { | |
{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL }, | ||
{ "persourcemaxstartups", sPerSourceMaxStartups, SSHCFG_GLOBAL }, | ||
{ "persourcenetblocksize", sPerSourceNetBlockSize, SSHCFG_GLOBAL }, | ||
{ "persourcepenalties", sPerSourcePenalties, SSHCFG_GLOBAL }, | ||
{ "persourcepenaltyexemptlist", sPerSourcePenaltyExemptList, SSHCFG_GLOBAL }, | ||
{ "maxauthtries", sMaxAuthTries, SSHCFG_ALL }, | ||
{ "maxsessions", sMaxSessions, SSHCFG_ALL }, | ||
{ "banner", sBanner, SSHCFG_ALL }, | ||
|
@@ -1945,6 +1977,89 @@ process_server_config_line_depth(ServerOptions *options, char *line, | |
options->per_source_max_startups = value; | ||
break; | ||
|
||
case sPerSourcePenaltyExemptList: | ||
charptr = &options->per_source_penalty_exempt; | ||
arg = argv_next(&ac, &av); | ||
if (!arg || *arg == '\0') | ||
fatal("%s line %d: missing file name.", | ||
filename, linenum); | ||
if (addr_match_list(NULL, arg) != 0) { | ||
fatal("%s line %d: keyword %s " | ||
"invalid address argument.", | ||
filename, linenum, keyword); | ||
} | ||
if (*activep && *charptr == NULL) | ||
*charptr = xstrdup(arg); | ||
break; | ||
|
||
case sPerSourcePenalties: | ||
while ((arg = argv_next(&ac, &av)) != NULL) { | ||
found = 1; | ||
value = -1; | ||
value2 = 0; | ||
p = NULL; | ||
/* Allow no/yes only in first position */ | ||
if (strcasecmp(arg, "no") == 0 || | ||
(value2 = (strcasecmp(arg, "yes") == 0))) { | ||
if (ac > 0) { | ||
fatal("%s line %d: keyword %s \"%s\" " | ||
"argument must appear alone.", | ||
filename, linenum, keyword, arg); | ||
} | ||
if (*activep && | ||
options->per_source_penalty.enabled == -1) | ||
options->per_source_penalty.enabled = value2; | ||
continue; | ||
} else if (strncmp(arg, "crash:", 6) == 0) { | ||
p = arg + 6; | ||
intptr = &options->per_source_penalty.penalty_crash; | ||
} else if (strncmp(arg, "authfail:", 9) == 0) { | ||
p = arg + 9; | ||
intptr = &options->per_source_penalty.penalty_authfail; | ||
} else if (strncmp(arg, "noauth:", 7) == 0) { | ||
p = arg + 7; | ||
intptr = &options->per_source_penalty.penalty_noauth; | ||
} else if (strncmp(arg, "grace-exceeded:", 15) == 0) { | ||
p = arg + 15; | ||
intptr = &options->per_source_penalty.penalty_grace; | ||
} else if (strncmp(arg, "max:", 4) == 0) { | ||
p = arg + 4; | ||
intptr = &options->per_source_penalty.penalty_max; | ||
} else if (strncmp(arg, "min:", 4) == 0) { | ||
p = arg + 4; | ||
intptr = &options->per_source_penalty.penalty_min; | ||
} else if (strncmp(arg, "max-sources:", 12) == 0) { | ||
intptr = &options->per_source_penalty.max_sources; | ||
if ((errstr = atoi_err(arg+12, &value)) != NULL) | ||
fatal("%s line %d: %s value %s.", | ||
filename, linenum, keyword, errstr); | ||
} else if (strcmp(arg, "overflow:deny-all") == 0) { | ||
intptr = &options->per_source_penalty.overflow_mode; | ||
value = PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL; | ||
} else if (strcmp(arg, "overflow:permissive") == 0) { | ||
intptr = &options->per_source_penalty.overflow_mode; | ||
value = PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE; | ||
} else { | ||
fatal("%s line %d: unsupported %s keyword %s", | ||
filename, linenum, keyword, arg); | ||
} | ||
/* If no value was parsed above, assume it's a time */ | ||
if (value == -1 && (value = convtime(p)) == -1) { | ||
fatal("%s line %d: invalid %s time value.", | ||
filename, linenum, keyword); | ||
} | ||
if (*activep && *intptr == -1) { | ||
*intptr = value; | ||
/* any option implicitly enables penalties */ | ||
options->per_source_penalty.enabled = 1; | ||
} | ||
} | ||
if (!found) { | ||
fatal("%s line %d: no %s specified", | ||
filename, linenum, keyword); | ||
} | ||
break; | ||
|
||
case sMaxAuthTries: | ||
intptr = &options->max_authtries; | ||
goto parse_int; | ||
|
@@ -3082,6 +3197,7 @@ dump_config(ServerOptions *o) | |
dump_cfg_string(sRDomain, o->routing_domain); | ||
#endif | ||
dump_cfg_string(sSshdSessionPath, o->sshd_session_path); | ||
dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt); | ||
|
||
/* string arguments requiring a lookup */ | ||
dump_cfg_string(sLogLevel, log_level_name(o->log_level)); | ||
|
@@ -3169,4 +3285,20 @@ dump_config(ServerOptions *o) | |
if (o->pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED) | ||
printf(" verify-required"); | ||
printf("\n"); | ||
|
||
if (o->per_source_penalty.enabled) { | ||
printf("persourcepenalties crash:%d authfail:%d noauth:%d " | ||
"grace-exceeded:%d max:%d min:%d max-sources:%d " | ||
"overflow:%s\n", o->per_source_penalty.penalty_crash, | ||
o->per_source_penalty.penalty_authfail, | ||
o->per_source_penalty.penalty_noauth, | ||
o->per_source_penalty.penalty_grace, | ||
o->per_source_penalty.penalty_max, | ||
o->per_source_penalty.penalty_min, | ||
o->per_source_penalty.max_sources, | ||
o->per_source_penalty.overflow_mode == | ||
PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL ? | ||
"deny-all" : "permissive"); | ||
} else | ||
printf("persourcepenalties no\n"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: servconf.h,v 1.163 2024/05/23 23:47:16 jsg Exp $ */ | ||
/* $OpenBSD: servconf.h,v 1.164 2024/06/06 17:15:25 djm Exp $ */ | ||
|
||
/* | ||
* Author: Tatu Ylonen <[email protected]> | ||
|
@@ -65,6 +65,20 @@ struct listenaddr { | |
struct addrinfo *addrs; | ||
}; | ||
|
||
#define PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL 1 | ||
#define PER_SOURCE_PENALTY_OVERFLOW_PERMISSIVE 2 | ||
struct per_source_penalty { | ||
int enabled; | ||
int max_sources; | ||
int overflow_mode; | ||
int penalty_crash; | ||
int penalty_grace; | ||
int penalty_authfail; | ||
int penalty_noauth; | ||
int penalty_max; | ||
int penalty_min; | ||
}; | ||
|
||
typedef struct { | ||
u_int num_ports; | ||
u_int ports_from_cmdline; | ||
|
@@ -172,6 +186,8 @@ typedef struct { | |
int per_source_max_startups; | ||
int per_source_masklen_ipv4; | ||
int per_source_masklen_ipv6; | ||
char *per_source_penalty_exempt; | ||
struct per_source_penalty per_source_penalty; | ||
int max_authtries; | ||
int max_sessions; | ||
char *banner; /* SSH-2 banner message */ | ||
|
Oops, something went wrong.