Added a configuration option named AuthFailHook. This option holds the path of a program to run in the event that sshd exits without successfully authenticating a network client. The program receives the client's ip address in its environment. This is meant as a means of blacklisting nodes that attempt to break into our system by trying long lists of common passwords, one by one. Delegating this task to an external program provides for maximum flexibility. --- servconf.c | 6 ++++++ servconf.h | 1 + sshd.c | 19 +++++++++++++++++++ sshd_config | 3 +++ sshd_config.5 | 11 +++++++++++ 5 files changed, 40 insertions(+) diff --git a/servconf.c b/servconf.c index 70f5f73f..d455e465 100644 --- a/servconf.c +++ b/servconf.c @@ -543,6 +543,7 @@ typedef enum { sStreamLocalBindMask, sStreamLocalBindUnlink, sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding, sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, + sAuthFailHook, sDeprecated, sIgnore, sUnsupported } ServerOpCodes; @@ -695,6 +696,7 @@ static struct { { "rdomain", sRDomain, SSHCFG_ALL }, { "casignaturealgorithms", sCASignatureAlgorithms, SSHCFG_ALL }, { "securitykeyprovider", sSecurityKeyProvider, SSHCFG_GLOBAL }, + { "authfailhook", sAuthFailHook, SSHCFG_ALL }, { NULL, sBadOption, 0 } }; @@ -2338,6 +2340,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, *charptr = xstrdup(arg); break; + case sAuthFailHook: + charptr = &options->auth_fail_hook; + goto parse_filename; + case sDeprecated: case sIgnore: case sUnsupported: diff --git a/servconf.h b/servconf.h index 4202a2d0..2946d169 100644 --- a/servconf.h +++ b/servconf.h @@ -218,6 +218,7 @@ typedef struct { int expose_userauth_info; u_int64_t timing_secret; char *sk_provider; + char *auth_fail_hook; } ServerOptions; /* Information about the incoming connection as used by Match */ diff --git a/sshd.c b/sshd.c index 60b2aaf7..446d7ce9 100644 --- a/sshd.c +++ b/sshd.c @@ -2385,12 +2385,30 @@ do_ssh2_kex(struct ssh *ssh) debug("KEX done"); } +/* Run an external program if client authentication was unsuccessful */ +static void +run_auth_fail_hook(const char * h, const char * ip) +{ + if ((use_privsep && !mm_is_monitor()) || !strcmp(ip, "UNKNOWN")) + return; + + debug3("%s: Running %s for ip %s", __func__, h, ip); + + if (setenv("SSH_NOAUTH_ADDRESS", ip, 1) != 0 || system(h) != 0) + error("%s: Failed to run authentification failure hook", + __func__); +} + /* server specific fatal cleanup */ void cleanup_exit(int i) { if (the_active_state != NULL && the_authctxt != NULL) { do_cleanup(the_active_state, the_authctxt); + if (options.auth_fail_hook != NULL && + !the_authctxt->authenticated) + run_auth_fail_hook(options.auth_fail_hook, + ssh_remote_ipaddr(the_active_state)); if (use_privsep && privsep_is_preauth && pmonitor != NULL && pmonitor->m_pid > 1) { debug("Killing privsep child %d", pmonitor->m_pid); @@ -2405,5 +2423,6 @@ cleanup_exit(int i) if (the_active_state != NULL && (!use_privsep || mm_is_monitor())) audit_event(the_active_state, SSH_CONNECTION_ABANDON); #endif + _exit(i); } diff --git a/sshd_config b/sshd_config index 19b7c91a..108c07cd 100644 --- a/sshd_config +++ b/sshd_config @@ -105,6 +105,9 @@ AuthorizedKeysFile .ssh/authorized_keys # no default banner path #Banner none +# Program to run if client not authenticated +#AuthFailHook /bin/true + # override default of no subsystems Subsystem sftp /usr/libexec/sftp-server diff --git a/sshd_config.5 b/sshd_config.5 index 70ccea44..94e48084 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -231,6 +231,17 @@ is enabled), .Qq password and .Qq publickey . +.It Cm AuthFailHook +Specifies a program to be run if sshd exits whithout sucessfully +authenticating a client. The program is run via the +.Xr system 3 +function. It is only executed if the login attempt arrived via a +network connection. The address of the originating node will be passed +to it in the +.Ev SSH_NOAUTH_ADDRESS +environment variable. This is meant to provide a means of blacklisting +hosts that attempt to break into the system by trying long lists of +common passwords. .It Cm AuthorizedKeysCommand Specifies a program to be used to look up the user's public keys. The program must be owned by root, not writable by group or others and -- 2.24.1
Hi, sifting through my system's logs, I noticed many break-in attempts by rogue ssh clients trying long lists of common passwords. For some time now I pondered different approaches to counter these, but could not come up with a solution that really satisfied me. I finally reached the conclusion that any countermeasures required support in sshd itself, and created the attached patch. If activated in sshd_config, an external program will be invoked every time a session is terminated without the requesting client being authenticated. The program is passed the offending client's IP address in its environment. It could then block the originating host, possibly after a predefined number of such events in a certain interval, by reconfiguring the system's firewall or similar means. Comments welcome. Thomas Koeller (1): sshd: Added authentication failure hook servconf.c | 6 ++++++ servconf.h | 1 + sshd.c | 19 +++++++++++++++++++ sshd_config | 3 +++ sshd_config.5 | 11 +++++++++++ 5 files changed, 40 insertions(+) -- 2.24.1
On 2020-03-11 21:39:52, Thomas Koeller wrote:> Hi, > > sifting through my system's logs, I noticed many break-in attempts by > rogue ssh clients trying long lists of common passwords. For some time > now I pondered different approaches to counter these, but could not come > up with a solution that really satisfied me.This sounds much like what fail2ban does already. Have you given that a try? Mike -- Mike Mattice mailto: mike at mattice dot org 4096R/88E60DDD print B148 B5B8 B7E9 10CF 6150 442A 655F E1FE 88E6 0DDD
On Wed, 2020-03-11 at 21:39 +0100, Thomas Koeller wrote:> an external program will be invoked every time a session > is > terminated without the requesting client being authenticated.IMO, the idea itself sounds not the best... one must assume that such invoked programs are not written "safe"... and thus an attacker could potentially cause the system to run such programs a huge number of times. Maybe they take a while to finish (or in error case: do not finish a all) thus causing DoS. Not to talk about further complex scenarios where such invocation might be used for analysis or other forms of attacks. Cheers, Chris.
On Thu, 12 Mar 2020, Christoph Anton Mitterer wrote:> IMO, the idea itself sounds not the best... one must assume that such > invoked programs are not written "safe"... and thus an attacker could > potentially cause the system to run such programs a huge number of > times.As for the original problem? I have this running under daemontools: #!/bin/mksh exec >/dev/null exec 2>/dev/null tail -f /var/log/messages | while IFS= read -r line; do [[ $line = *sshd*@(Failed password for ?(invalid user )@(root|sync|admin|oracle|pi|setup|test|testuser|ubnt) from)* ]] || continue line=${line#*for ?(invalid user )@(root|sync|admin|oracle|pi|setup|test|testuser|ubnt) from } line=${line%% *} [[ $line = +([0-9]).+([0-9]).+([0-9]).+([0-9]) ]] || continue x=$(pfctl -t theo -T add "$line" 2>&1) logger -t sshnuke "Blocking '$line': $x" done The pf table named ?theo? is a ?block everything? table. The list of account names which trigger blocking gets updated every once in a while. This currently only works on Legacy IP but updating it to also support IP should be trivial. It blocks after the first attempt, which is why I only catch known-bad account names, not typos. Getting back in if you accidentally blocked yourself is outside of the scope of this. You?ll need tail -F for GNU systems. bye, //mirabilos -- ?Cool, /usr/share/doc/mksh/examples/uhr.gz ist ja ein Grund, mksh auf jedem System zu installieren.? -- XTaran auf der OpenRheinRuhr, ganz begeistert (EN: ?[?]uhr.gz is a reason to install mksh on every system.?)
On 12.03.20 19:09, Christoph Anton Mitterer wrote:> On Wed, 2020-03-11 at 21:39 +0100, Thomas Koeller wrote: > IMO, the idea itself sounds not the best... one must assume that such > invoked programs are not written "safe"... and thus an attacker could > potentially cause the system to run such programs a huge number of > times.As the anticipated action of the program is to blacklist hosts, this would require some kind of DDOS attack, using a botnet or the like.> > Maybe they take a while to finish (or in error case: do not finish a > all) thus causing DoS. > > Not to talk about further complex scenarios where such invocation might > be used for analysis or other forms of attacks.While it is certainly true that poorly written programs can do harm, please keep in mind that the only way for an attacker to interact with the spawned program is to cause it to run. He cannot influence what the program does, so any problems it may cause are the writer's fault. Thomas
On 2020-03-11 21:39:52, Thomas Koeller wrote: > Hi, > > sifting through my system's logs, I noticed many break-in attempts by > rogue ssh clients trying long lists of common passwords. For some time > now I pondered different approaches to counter these, but could not come > up with a solution that really satisfied me. This sounds much like what fail2ban does already. Have you given that a try? Mike