From d92381aaa92129ef73d622a59d09e4901b14d677 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 18 Jul 2018 15:23:56 +0200 Subject: [PATCH 1/3] fail2ban-regex: ignore lines having not empty match of `` from failregex (not a failure, so count as ignored and not as matched). --- fail2ban/client/fail2banregex.py | 19 +++++++++++++------ fail2ban/tests/fail2banregextestcase.py | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py index 6add0eaa..68b7b7c3 100644 --- a/fail2ban/client/fail2banregex.py +++ b/fail2ban/client/fail2banregex.py @@ -411,17 +411,23 @@ class Fail2banRegex(object): def testRegex(self, line, date=None): orgLineBuffer = self._filter._Filter__lineBuffer fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines() + is_ignored = False try: - ret = self._filter.processLine(line, date) + found = self._filter.processLine(line, date) lines = [] line = self._filter.processedLine() - for match in ret: + ret = [] + for match in found: # Append True/False flag depending if line was matched by # more than one regex match.append(len(ret)>1) regex = self._failregex[match[0]] regex.inc() regex.appendIP(match) + if not match[3].get('nofail'): + ret.append(match) + else: + is_ignored = True except RegexException as e: # pragma: no cover output( 'ERROR: %s' % e ) return False @@ -447,13 +453,13 @@ class Fail2banRegex(object): if lines: # pre-lines parsed in multiline mode (buffering) lines.append(line) line = "\n".join(lines) - return line, ret + return line, ret, is_ignored def process(self, test_lines): t0 = time.time() for line in test_lines: if isinstance(line, tuple): - line_datetimestripped, ret = self.testRegex( + line_datetimestripped, ret, is_ignored = self.testRegex( line[0], line[1]) line = "".join(line[0]) else: @@ -461,8 +467,9 @@ class Fail2banRegex(object): if line.startswith('#') or not line: # skip comment and empty lines continue - line_datetimestripped, ret = self.testRegex(line) - is_ignored = self.testIgnoreRegex(line_datetimestripped) + line_datetimestripped, ret, is_ignored = self.testRegex(line) + if not is_ignored: + is_ignored = self.testIgnoreRegex(line_datetimestripped) if is_ignored: self._line_stats.ignored += 1 diff --git a/fail2ban/tests/fail2banregextestcase.py b/fail2ban/tests/fail2banregextestcase.py index c3919230..44acfd35 100644 --- a/fail2ban/tests/fail2banregextestcase.py +++ b/fail2ban/tests/fail2banregextestcase.py @@ -209,7 +209,7 @@ class Fail2banRegexTest(LogCaptureTestCase): def testVerboseFullSshd(self): (opts, args, fail2banRegex) = _Fail2banRegex( "-l", "notice", # put down log-level, because of too many debug-messages - "-v", "--verbose-date", "--print-all-matched", + "-v", "--verbose-date", "--print-all-matched", "--print-all-ignored", "-c", CONFIG_DIR, Fail2banRegexTest.FILENAME_SSHD, "sshd" ) From 64d9e164cfbd5bdf12398d25b25f9a48c1174ec0 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 18 Jul 2018 15:30:06 +0200 Subject: [PATCH 2/3] extends samples test-case factory to see the matched regex number and expression in assert message (helps if some similar regexp's available in filter) --- fail2ban/tests/samplestestcase.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fail2ban/tests/samplestestcase.py b/fail2ban/tests/samplestestcase.py index 1d326c31..7f03b96a 100644 --- a/fail2ban/tests/samplestestcase.py +++ b/fail2ban/tests/samplestestcase.py @@ -201,6 +201,7 @@ def testSampleRegexsFactory(name, basedir): flt, regexsUsedIdx = flt regexList = flt.getFailRegex() + failregex = -1 try: fail = {} ret = flt.processLine(line) @@ -263,9 +264,10 @@ def testSampleRegexsFactory(name, basedir): regexsUsedRe.add(regexList[failregex]) except AssertionError as e: # pragma: no cover import pprint - raise AssertionError("%s: %s on: %s:%i, line:\n%s\n" + raise AssertionError("%s: %s on: %s:%i, line:\n %sregex (%s):\n %s\n" "faildata: %s\nfail: %s" % ( - fltName, e, logFile.filename(), logFile.filelineno(), line, + fltName, e, logFile.filename(), logFile.filelineno(), + line, failregex, regexList[failregex] if failregex != -1 else None, '\n'.join(pprint.pformat(faildata).splitlines()), '\n'.join(pprint.pformat(fail).splitlines()))) From 22d37cdce21a7200b82c432348ff725dabd5c28b Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 18 Jul 2018 15:31:04 +0200 Subject: [PATCH 3/3] sshd: fixed failregex for ddos (resp. aggressive) mode, to cover "authenticating user" case in log-message: Connection closed by authenticating user root 192.0.2.10 ... [preauth] tests extended (also with few injection tries). closes gh-2185. --- config/filter.d/sshd.conf | 8 +++++--- .../config/filter.d/zzz-sshd-obsolete-multiline.conf | 5 ++++- fail2ban/tests/files/logs/sshd | 9 +++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index 60b61241..60efead7 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -24,6 +24,8 @@ __pref = (?:(?:error|fatal): (?:PAM: )?)? #__suff = (?: port \d+)?(?: \[preauth\])?\s* __suff = (?: (?:port \d+|on \S+|\[preauth\])){0,3}\s* __on_port_opt = (?: (?:port \d+|on \S+)){0,2} +# close by authenticating user: +__authng_user = (?: authenticating user \S+|.+?)? # for all possible (also future) forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found", # see ssherr.c for all possible SSH_ERR_..._ALG_MATCH errors. @@ -54,16 +56,16 @@ cmnfailre = ^[aA]uthentication (?:failure|error|failed) for .* ^User .+ not allowed because account is locked%(__suff)s ^Disconnecting: Too many authentication failures(?: for .+?)?%(__suff)s$ ^Received disconnect from %(__on_port_opt)s:\s*11: - ^Connection closed by -suff-onclosed> + ^Connection closed by%(__authng_user)s -suff-onclosed> ^Accepted \w+ for \S+ from (?:\s|$) mdre-normal = # used to differentiate "connection closed" with and without `[preauth]` (fail/nofail cases in ddos mode) -mdrp-normal-suff-onclosed = +mdrp-normal-suff-onclosed = (?:%(__suff)s|\s*)$ mdre-ddos = ^Did not receive identification string from ^Connection reset by - ^Connection closed by %(__on_port_opt)s\s+\[preauth\]\s*$ + ^Connection closed by%(__authng_user)s %(__on_port_opt)s\s+\[preauth\]\s*$ ^SSH: Server;Ltype: (?:Authname|Version|Kex);Remote: -\d+;[A-Z]\w+: ^Read from socket failed: Connection reset by peer mdrp-ddos-suff-onclosed = %(__on_port_opt)s\s*$ diff --git a/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf b/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf index 5717c316..98fca7f5 100644 --- a/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf +++ b/fail2ban/tests/config/filter.d/zzz-sshd-obsolete-multiline.conf @@ -16,6 +16,8 @@ __pref = (?:(?:error|fatal): (?:PAM: )?)? # optional suffix (logged from several ssh versions) like " [preauth]" __suff = (?: (?:port \d+|on \S+|\[preauth\])){0,3}\s* __on_port_opt = (?: (?:port \d+|on \S+)){0,2} +# close by authenticating user: +__authng_user = (?: authenticating user \S+|.+?)? # single line prefix: __prefix_line_sl = %(__prefix_line)s%(__pref)s @@ -48,12 +50,13 @@ cmnfailre = ^%(__prefix_line_sl)s[aA]uthentication (?:failure|error|failed) for ^%(__prefix_line_ml1)s%(__pam_auth)s\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=\s.*%(__suff)s$%(__prefix_line_ml2)sConnection closed ^%(__prefix_line_sl)s(error: )?maximum authentication attempts exceeded for .* from %(__on_port_opt)s(?: ssh\d*)? \[preauth\]$ ^%(__prefix_line_ml1)sUser .+ not allowed because account is locked%(__prefix_line_ml2)sReceived disconnect from %(__on_port_opt)s:\s*11: .+%(__suff)s$ - ^%(__prefix_line_ml1)sDisconnecting: Too many authentication failures(?: for .+?)?%(__suff)s%(__prefix_line_ml2)sConnection closed by %(__suff)s$ + ^%(__prefix_line_ml1)sDisconnecting: Too many authentication failures(?: for .+?)?%(__suff)s%(__prefix_line_ml2)sConnection closed by%(__authng_user)s %(__suff)s$ ^%(__prefix_line_ml1)sConnection from %(__on_port_opt)s%(__prefix_line_ml2)sDisconnecting: Too many authentication failures(?: for .+?)?%(__suff)s$ mdre-normal = mdre-ddos = ^%(__prefix_line_sl)sDid not receive identification string from + ^%(__prefix_line_sl)sConnection closed by%(__authng_user)s %(__on_port_opt)s\s+\[preauth\]\s*$ ^%(__prefix_line_sl)sConnection reset by ^%(__prefix_line_ml1)sSSH: Server;Ltype: (?:Authname|Version|Kex);Remote: -\d+;[A-Z]\w+:.*%(__prefix_line_ml2)sRead from socket failed: Connection reset by peer%(__suff)s$ diff --git a/fail2ban/tests/files/logs/sshd b/fail2ban/tests/files/logs/sshd index 21629fb7..e2b3d456 100644 --- a/fail2ban/tests/files/logs/sshd +++ b/fail2ban/tests/files/logs/sshd @@ -283,6 +283,13 @@ Nov 24 23:46:43 host sshd[32686]: fatal: Read from socket failed: Connection res # failJSON: { "time": "2005-03-15T09:20:57", "match": true , "host": "192.0.2.39", "desc": "Singleline for connection reset by" } Mar 15 09:20:57 host sshd[28972]: Connection reset by 192.0.2.39 port 14282 [preauth] +# failJSON: { "time": "2005-07-17T23:03:05", "match": true , "host": "192.0.2.10", "user": "root", "desc": "user name additionally, gh-2185" } +Jul 17 23:03:05 srv sshd[1296]: Connection closed by authenticating user root 192.0.2.10 port 46038 [preauth] +# failJSON: { "time": "2005-07-17T23:04:00", "match": true , "host": "192.0.2.11", "user": "test 127.0.0.1", "desc": "check inject on username, gh-2185" } +Jul 17 23:04:00 srv sshd[1300]: Connection closed by authenticating user test 127.0.0.1 192.0.2.11 port 46039 [preauth] +# failJSON: { "time": "2005-07-17T23:04:01", "match": true , "host": "192.0.2.11", "user": "test 127.0.0.1 port 12345", "desc": "check inject on username, gh-2185" } +Jul 17 23:04:01 srv sshd[1300]: Connection closed by authenticating user test 127.0.0.1 port 12345 192.0.2.11 port 46039 [preauth] + # filterOptions: [{"test.condition":"name=='sshd'", "mode": "ddos"}, {"test.condition":"name=='sshd'", "mode": "aggressive"}] # failJSON: { "time": "2005-03-15T09:21:01", "match": true , "host": "192.0.2.212", "desc": "DDOS mode causes failure on close within preauth stage" } @@ -290,8 +297,6 @@ Mar 15 09:21:01 host sshd[2717]: Connection closed by 192.0.2.212 [preauth] # failJSON: { "time": "2005-03-15T09:21:02", "match": true , "host": "192.0.2.212", "desc": "DDOS mode causes failure on close within preauth stage" } Mar 15 09:21:02 host sshd[2717]: Connection closed by 192.0.2.212 [preauth] - - # filterOptions: [{"mode": "extra"}, {"mode": "aggressive"}] # several other cases from gh-864: