mirror of https://github.com/fail2ban/fail2ban
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
parent
ac8e8db814
commit
1492ab2247
|
@ -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|$)
|
||||||
|
|
|
@ -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')))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue