improve processing of pending failures (lines without ID/IP) - fail2ban-regex would show those in matched lines now (as well as increase count of matched RE);

avoid overwrite of data with empty tags by ticket constructed from multi-line failures;
amend to d1b7e2b5fb2b389d04845369d7d29db65425dcf2: better output (as well as ignoring of pending lines) using `--out msg`;
filter.d/sshd.conf: don't forget mlf-cache on "disconnecting: too many authentication failures" - message does not have IP (must be followed by "closed [preauth]" to obtain host-IP).
pull/2638/head
sebres 2020-02-11 18:44:36 +01:00
parent ac8e8db814
commit 1492ab2247
5 changed files with 22 additions and 26 deletions

View File

@ -55,7 +55,7 @@ cmnfailre = ^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER>
^(error: )?maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$ ^(error: )?maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
^User <F-USER>.+</F-USER> not allowed because account is locked%(__suff)s ^User <F-USER>.+</F-USER> not allowed because account is locked%(__suff)s
^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+</F-USER> <HOST>%(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$ ^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+</F-USER> <HOST>%(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$
^<F-MLFFORGET>Disconnecting</F-MLFFORGET>: Too many authentication failures(?: for <F-USER>.+?</F-USER>)?%(__suff)s$ ^Disconnecting: Too many authentication failures(?: for <F-USER>.+?</F-USER>)?%(__suff)s$
^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>%(__on_port_opt)s:\s*11: ^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>%(__on_port_opt)s:\s*11:
<mdre-<mode>-other> <mdre-<mode>-other>
^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$) ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)

View File

@ -272,6 +272,8 @@ class Fail2banRegex(object):
self._filter.returnRawHost = opts.raw self._filter.returnRawHost = opts.raw
self._filter.checkFindTime = False self._filter.checkFindTime = False
self._filter.checkAllRegex = opts.checkAllRegex and not opts.out self._filter.checkAllRegex = opts.checkAllRegex and not opts.out
# ignore pending (without ID/IP), added to matches if it hits later (if ID/IP can be retreved)
self._filter.ignorePending = opts.out;
self._backend = 'auto' self._backend = 'auto'
def output(self, line): def output(self, line):
@ -452,7 +454,6 @@ class Fail2banRegex(object):
try: try:
found = self._filter.processLine(line, date) found = self._filter.processLine(line, date)
lines = [] lines = []
line = self._filter.processedLine()
ret = [] ret = []
for match in found: for match in found:
# Append True/False flag depending if line was matched by # Append True/False flag depending if line was matched by
@ -488,7 +489,7 @@ class Fail2banRegex(object):
self._line_stats.matched += 1 self._line_stats.matched += 1
self._line_stats.missed -= 1 self._line_stats.missed -= 1
if lines: # pre-lines parsed in multiline mode (buffering) if lines: # pre-lines parsed in multiline mode (buffering)
lines.append(line) lines.append(self._filter.processedLine())
line = "\n".join(lines) line = "\n".join(lines)
return line, ret, is_ignored return line, ret, is_ignored
@ -523,7 +524,8 @@ class Fail2banRegex(object):
output(ret[1]) output(ret[1])
elif self._opts.out == 'msg': elif self._opts.out == 'msg':
for ret in ret: for ret in ret:
output('\n'.join(map(lambda v:''.join(v for v in v), ret[3].get('matches')))) for ret in ret[3].get('matches'):
output(''.join(v for v in ret))
elif self._opts.out == 'row': elif self._opts.out == 'row':
for ret in ret: for ret in ret:
output('[%r,\t%r,\t%r],' % (ret[1],ret[2],dict((k,v) for k, v in ret[3].iteritems() if k != 'matches'))) output('[%r,\t%r,\t%r],' % (ret[1],ret[2],dict((k,v) for k, v in ret[3].iteritems() if k != 'matches')))

View File

@ -105,6 +105,8 @@ class Filter(JailThread):
self.returnRawHost = False self.returnRawHost = False
## check each regex (used for test purposes): ## check each regex (used for test purposes):
self.checkAllRegex = False self.checkAllRegex = False
## avoid finding of pending failures (without ID/IP, used in fail2ban-regex):
self.ignorePending = True
## if true ignores obsolete failures (failure time < now - findTime): ## if true ignores obsolete failures (failure time < now - findTime):
self.checkFindTime = True self.checkFindTime = True
## Ticks counter ## Ticks counter
@ -651,7 +653,7 @@ class Filter(JailThread):
fail['users'] = users = set() fail['users'] = users = set()
users.add(user) users.add(user)
return users return users
return None return users
# # ATM incremental (non-empty only) merge deactivated ... # # ATM incremental (non-empty only) merge deactivated ...
# @staticmethod # @staticmethod
@ -680,25 +682,22 @@ class Filter(JailThread):
if not fail.get('nofail'): if not fail.get('nofail'):
fail['nofail'] = fail["mlfgained"] fail['nofail'] = fail["mlfgained"]
elif fail.get('nofail'): nfflgs |= 1 elif fail.get('nofail'): nfflgs |= 1
if fail.get('mlfforget'): nfflgs |= 2 if fail.pop('mlfforget', None): nfflgs |= 2
# if multi-line failure id (connection id) known: # if multi-line failure id (connection id) known:
if mlfidFail: if mlfidFail:
mlfidGroups = mlfidFail[1] mlfidGroups = mlfidFail[1]
# update users set (hold all users of connect): # update users set (hold all users of connect):
users = self._updateUsers(mlfidGroups, fail.get('user')) users = self._updateUsers(mlfidGroups, fail.get('user'))
# be sure we've correct current state ('nofail' and 'mlfgained' only from last failure) # be sure we've correct current state ('nofail' and 'mlfgained' only from last failure)
try: mlfidGroups.pop('nofail', None)
del mlfidGroups['nofail'] mlfidGroups.pop('mlfgained', None)
del mlfidGroups['mlfgained']
except KeyError:
pass
# # ATM incremental (non-empty only) merge deactivated (for future version only), # # ATM incremental (non-empty only) merge deactivated (for future version only),
# # it can be simulated using alternate value tags, like <F-ALT_VAL>...</F-ALT_VAL>, # # it can be simulated using alternate value tags, like <F-ALT_VAL>...</F-ALT_VAL>,
# # so previous value 'val' will be overwritten only if 'alt_val' is not empty... # # so previous value 'val' will be overwritten only if 'alt_val' is not empty...
# _updateFailure(mlfidGroups, fail) # _updateFailure(mlfidGroups, fail)
# #
# overwrite multi-line failure with all values, available in fail: # overwrite multi-line failure with all values, available in fail:
mlfidGroups.update(fail) mlfidGroups.update(((k,v) for k,v in fail.iteritems() if v is not None))
# new merged failure data: # new merged failure data:
fail = mlfidGroups fail = mlfidGroups
# if forget (disconnect/reset) - remove cached entry: # if forget (disconnect/reset) - remove cached entry:
@ -709,20 +708,14 @@ class Filter(JailThread):
mlfidFail = [self.__lastDate, fail] mlfidFail = [self.__lastDate, fail]
self.mlfidCache.set(mlfid, mlfidFail) self.mlfidCache.set(mlfid, mlfidFail)
# check users in order to avoid reset failure by multiple logon-attempts: # check users in order to avoid reset failure by multiple logon-attempts:
if users and len(users) > 1: if fail.pop('mlfpending', 0) or users and len(users) > 1:
# we've new user, reset 'nofail' because of multiple users attempts: # we've new user, reset 'nofail' because of multiple users attempts:
try: fail.pop('nofail', None)
del fail['nofail']
nfflgs &= ~1 # reset nofail nfflgs &= ~1 # reset nofail
except KeyError:
pass
# merge matches: # merge matches:
if not (nfflgs & 1): # current nofail state (corresponding users) if not (nfflgs & 1): # current nofail state (corresponding users)
try: m = fail.pop("nofail-matches", [])
m = fail.pop("nofail-matches")
m += fail.get("matches", []) m += fail.get("matches", [])
except KeyError:
m = fail.get("matches", [])
if not (nfflgs & 8): # no gain signaled if not (nfflgs & 8): # no gain signaled
m += failRegex.getMatchedTupleLines() m += failRegex.getMatchedTupleLines()
fail["matches"] = m fail["matches"] = m
@ -888,7 +881,8 @@ class Filter(JailThread):
if host is None: if host is None:
if ll <= 7: logSys.log(7, "No failure-id by mlfid %r in regex %s: %s", if ll <= 7: logSys.log(7, "No failure-id by mlfid %r in regex %s: %s",
mlfid, failRegexIndex, fail.get('mlfforget', "waiting for identifier")) mlfid, failRegexIndex, fail.get('mlfforget', "waiting for identifier"))
if not self.checkAllRegex: return failList fail['mlfpending'] = 1; # mark failure is pending
if not self.checkAllRegex and self.ignorePending: return failList
ips = [None] ips = [None]
# if raw - add single ip or failure-id, # if raw - add single ip or failure-id,
# otherwise expand host to multiple ips using dns (or ignore it if not valid): # otherwise expand host to multiple ips using dns (or ignore it if not valid):

View File

@ -134,7 +134,7 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 po
# failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" } # failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4 Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "time": "2004-09-29T17:15:03", "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" } # failJSON: { "time": "2004-09-29T17:15:03", "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
Sep 29 17:15:03 spaceman sshd[12946]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4 Sep 29 17:15:03 spaceman sshd[12947]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" } # failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2 Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2

View File

@ -135,7 +135,7 @@ srv sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser
# failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" } # failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
srv sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4 srv sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" } # failJSON: { "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
srv sshd[12946]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4 srv sshd[12947]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" } # failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
srv sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2 srv sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2