From 7d112430caabfb6f5922545adca77b1a210a3185 Mon Sep 17 00:00:00 2001 From: Jason Martin Date: Wed, 16 Apr 2014 21:21:41 -0700 Subject: [PATCH 1/5] Block brute-force attempts against the Monit gui --- config/filter.d/monit.conf | 18 ++++++++++++++++++ config/jail.conf | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 config/filter.d/monit.conf diff --git a/config/filter.d/monit.conf b/config/filter.d/monit.conf new file mode 100644 index 00000000..f32eae61 --- /dev/null +++ b/config/filter.d/monit.conf @@ -0,0 +1,18 @@ +# Fail2Ban filter for monit.conf, looks for failed access attempts +# +# + +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + +[Definition] +# Samples: +# [PDT Apr 16 20:59:11] error : Warning: Client '1.2.3.4' supplied unknown user 'foo' accessing monit httpd +# [PDT Apr 16 20:59:33] error : Warning: Client '1.2.3.4' supplied wrong password for user 'admin' accessing monit httpd + +failregex = Warning: Client '' supplied + +ignoreregex = diff --git a/config/jail.conf b/config/jail.conf index 96b3096f..7f7a7cbe 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -366,6 +366,12 @@ maxretry = 5 port = http,https logpath = /var/log/tomcat*/catalina.out +[monit] +#Ban clients brute-forcing the monit gui login +filter = monit +port = 2812 +logpath = /var/log/monit + [webmin-auth] From 6a740f684a52f4ccc310e75d17b18ec610c70bb0 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 18 Apr 2014 23:27:30 +0100 Subject: [PATCH 2/5] ENH: Move traceback formatter to from tests.utils to helpers Now allows for tests to be removed from package if desired --- bin/fail2ban-regex | 2 +- bin/fail2ban-testcases | 3 +- fail2ban/helpers.py | 83 ++++++++++++++++++++++++++++++++- fail2ban/tests/misctestcase.py | 3 +- fail2ban/tests/utils.py | 84 ++-------------------------------- 5 files changed, 91 insertions(+), 84 deletions(-) diff --git a/bin/fail2ban-regex b/bin/fail2ban-regex index ef198dcb..f1d5bdb9 100755 --- a/bin/fail2ban-regex +++ b/bin/fail2ban-regex @@ -45,7 +45,7 @@ from fail2ban.client.filterreader import FilterReader from fail2ban.server.filter import Filter from fail2ban.server.failregex import RegexException -from fail2ban.tests.utils import FormatterWithTraceBack +from fail2ban.helpers import FormatterWithTraceBack # Gets the instance of the logger. logSys = logging.getLogger("fail2ban") diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases index b3bddf1c..0e2fdb4b 100755 --- a/bin/fail2ban-testcases +++ b/bin/fail2ban-testcases @@ -34,7 +34,8 @@ if os.path.exists("fail2ban/__init__.py"): sys.path.insert(0, ".") from fail2ban.version import version -from fail2ban.tests.utils import FormatterWithTraceBack, gatherTests +from fail2ban.tests.utils import gatherTests +from fail2ban.helpers import FormatterWithTraceBack from fail2ban.server.mytime import MyTime from optparse import OptionParser, Option diff --git a/fail2ban/helpers.py b/fail2ban/helpers.py index 74ea7a7a..2579381d 100644 --- a/fail2ban/helpers.py +++ b/fail2ban/helpers.py @@ -20,9 +20,90 @@ __author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko" __license__ = "GPL" +import sys +import os +import traceback +import re +import logging def formatExceptionInfo(): """ Consistently format exception information """ - import sys cla, exc = sys.exc_info()[:2] return (cla.__name__, str(exc)) + +# +# Following "traceback" functions are adopted from PyMVPA distributed +# under MIT/Expat and copyright by PyMVPA developers (i.e. me and +# Michael). Hereby I re-license derivative work on these pieces under GPL +# to stay in line with the main Fail2Ban license +# +def mbasename(s): + """Custom function to include directory name if filename is too common + + Also strip .py at the end + """ + base = os.path.basename(s) + if base.endswith('.py'): + base = base[:-3] + if base in set(['base', '__init__']): + base = os.path.basename(os.path.dirname(s)) + '.' + base + return base + +class TraceBack(object): + """Customized traceback to be included in debug messages + """ + + def __init__(self, compress=False): + """Initialize TrackBack metric + + Parameters + ---------- + compress : bool + if True then prefix common with previous invocation gets + replaced with ... + """ + self.__prev = "" + self.__compress = compress + + def __call__(self): + ftb = traceback.extract_stack(limit=100)[:-2] + entries = [ + [mbasename(x[0]), os.path.dirname(x[0]), str(x[1])] for x in ftb] + entries = [ [e[0], e[2]] for e in entries + if not (e[0] in ['unittest', 'logging.__init__'] + or e[1].endswith('/unittest'))] + + # lets make it more concise + entries_out = [entries[0]] + for entry in entries[1:]: + if entry[0] == entries_out[-1][0]: + entries_out[-1][1] += ',%s' % entry[1] + else: + entries_out.append(entry) + sftb = '>'.join(['%s:%s' % (mbasename(x[0]), + x[1]) for x in entries_out]) + if self.__compress: + # lets remove part which is common with previous invocation + prev_next = sftb + common_prefix = os.path.commonprefix((self.__prev, sftb)) + common_prefix2 = re.sub('>[^>]*$', '', common_prefix) + + if common_prefix2 != "": + sftb = '...' + sftb[len(common_prefix2):] + self.__prev = prev_next + + return sftb + +class FormatterWithTraceBack(logging.Formatter): + """Custom formatter which expands %(tb) and %(tbc) with tracebacks + + TODO: might need locking in case of compressed tracebacks + """ + def __init__(self, fmt, *args, **kwargs): + logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs) + compress = '%(tbc)s' in fmt + self._tb = TraceBack(compress=compress) + + def format(self, record): + record.tbc = record.tb = self._tb() + return logging.Formatter.format(self, record) diff --git a/fail2ban/tests/misctestcase.py b/fail2ban/tests/misctestcase.py index 284b684b..ca84eba7 100644 --- a/fail2ban/tests/misctestcase.py +++ b/fail2ban/tests/misctestcase.py @@ -32,8 +32,7 @@ import datetime from glob import glob from StringIO import StringIO -from .utils import mbasename, TraceBack, FormatterWithTraceBack -from ..helpers import formatExceptionInfo +from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack from ..server.datetemplate import DatePatternRegex diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index 85c1d929..7727632e 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -22,90 +22,17 @@ __author__ = "Yaroslav Halchenko" __copyright__ = "Copyright (c) 2013 Yaroslav Halchenko" __license__ = "GPL" -import logging, os, re, traceback, time, unittest -from os.path import basename, dirname +import logging +import os +import re +import time +import unittest from StringIO import StringIO from ..server.mytime import MyTime logSys = logging.getLogger(__name__) -# -# Following "traceback" functions are adopted from PyMVPA distributed -# under MIT/Expat and copyright by PyMVPA developers (i.e. me and -# Michael). Hereby I re-license derivative work on these pieces under GPL -# to stay in line with the main Fail2Ban license -# -def mbasename(s): - """Custom function to include directory name if filename is too common - - Also strip .py at the end - """ - base = basename(s) - if base.endswith('.py'): - base = base[:-3] - if base in set(['base', '__init__']): - base = basename(dirname(s)) + '.' + base - return base - -class TraceBack(object): - """Customized traceback to be included in debug messages - """ - - def __init__(self, compress=False): - """Initialize TrackBack metric - - Parameters - ---------- - compress : bool - if True then prefix common with previous invocation gets - replaced with ... - """ - self.__prev = "" - self.__compress = compress - - def __call__(self): - ftb = traceback.extract_stack(limit=100)[:-2] - entries = [[mbasename(x[0]), dirname(x[0]), str(x[1])] for x in ftb] - entries = [ [e[0], e[2]] for e in entries - if not (e[0] in ['unittest', 'logging.__init__'] - or e[1].endswith('/unittest'))] - - # lets make it more concise - entries_out = [entries[0]] - for entry in entries[1:]: - if entry[0] == entries_out[-1][0]: - entries_out[-1][1] += ',%s' % entry[1] - else: - entries_out.append(entry) - sftb = '>'.join(['%s:%s' % (mbasename(x[0]), - x[1]) for x in entries_out]) - if self.__compress: - # lets remove part which is common with previous invocation - prev_next = sftb - common_prefix = os.path.commonprefix((self.__prev, sftb)) - common_prefix2 = re.sub('>[^>]*$', '', common_prefix) - - if common_prefix2 != "": - sftb = '...' + sftb[len(common_prefix2):] - self.__prev = prev_next - - return sftb - -class FormatterWithTraceBack(logging.Formatter): - """Custom formatter which expands %(tb) and %(tbc) with tracebacks - - TODO: might need locking in case of compressed tracebacks - """ - def __init__(self, fmt, *args, **kwargs): - logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs) - compress = '%(tbc)s' in fmt - self._tb = TraceBack(compress=compress) - - def format(self, record): - record.tbc = record.tb = self._tb() - return logging.Formatter.format(self, record) - def mtimesleep(): # no sleep now should be necessary since polling tracks now not only # mtime but also ino and size @@ -146,7 +73,6 @@ def gatherTests(regexps=None, no_network=False): if not regexps: # pragma: no cover tests = unittest.TestSuite() else: # pragma: no cover - import re class FilteredTestSuite(unittest.TestSuite): _regexps = [re.compile(r) for r in regexps] def addTest(self, suite): From 72bfd1433032c8790d2f5add267076798eed7ce5 Mon Sep 17 00:00:00 2001 From: Jason Martin Date: Sat, 19 Apr 2014 12:58:03 -0700 Subject: [PATCH 3/5] Tidy up filter.d/monit.conf, make regex more complete. Add ChangeLog / THANKS entry. Add test cases. --- ChangeLog | 1 + THANKS | 1 + config/filter.d/monit.conf | 13 ++----------- fail2ban/tests/files/logs/monit | 6 ++++++ 4 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 fail2ban/tests/files/logs/monit diff --git a/ChangeLog b/ChangeLog index 66df9639..7113d431 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,7 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger * Nginx filter to support missing server_name. Closes gh-676 - New features: + - Added monit filter thanks Jason H Martin. - Enhancements diff --git a/THANKS b/THANKS index 2c084dee..27165492 100644 --- a/THANKS +++ b/THANKS @@ -48,6 +48,7 @@ Ivo Truxa John Thoe Jacques Lav!gnotte Ioan Indreias +Jason H Martin Jonathan Kamens Jonathan Lanning Jonathan Underwood diff --git a/config/filter.d/monit.conf b/config/filter.d/monit.conf index f32eae61..04d01b20 100644 --- a/config/filter.d/monit.conf +++ b/config/filter.d/monit.conf @@ -2,17 +2,8 @@ # # -[INCLUDES] - -# Read common prefixes. If any customizations available -- read them from -# common.local -before = common.conf - [Definition] -# Samples: -# [PDT Apr 16 20:59:11] error : Warning: Client '1.2.3.4' supplied unknown user 'foo' accessing monit httpd -# [PDT Apr 16 20:59:33] error : Warning: Client '1.2.3.4' supplied wrong password for user 'admin' accessing monit httpd -failregex = Warning: Client '' supplied +failregex = \]\s*error\s*:\s*Warning:\s+Client '' supplied unknown user '\w+' accessing monit httpd$ + \]\s*error\s*:\s*Warning:\s+Client '' supplied wrong password for user '\w+' accessing monit httpd$ -ignoreregex = diff --git a/fail2ban/tests/files/logs/monit b/fail2ban/tests/files/logs/monit new file mode 100644 index 00000000..a923b6e2 --- /dev/null +++ b/fail2ban/tests/files/logs/monit @@ -0,0 +1,6 @@ +# failJSON: { "time": "2005-04-16T21:05:29", "match": true , "host": "69.93.127.111" } +[PDT Apr 16 21:05:29] error : Warning: Client '69.93.127.111' supplied unknown user 'foo' accessing monit httpd + +# failJSON: { "time": "2005-04-16T20:59:33", "match": true , "host": "97.113.189.111" } +[PDT Apr 16 20:59:33] error : Warning: Client '97.113.189.111' supplied wrong password for user 'admin' accessing monit httpd + From 9c3cb31862f8e0b31d12f0d02a9d114979cf3cd6 Mon Sep 17 00:00:00 2001 From: Jason Martin Date: Tue, 22 Apr 2014 21:29:52 -0700 Subject: [PATCH 4/5] Even stricter monit regex, now covers entire line --- config/filter.d/monit.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/filter.d/monit.conf b/config/filter.d/monit.conf index 04d01b20..1fcd980b 100644 --- a/config/filter.d/monit.conf +++ b/config/filter.d/monit.conf @@ -4,6 +4,6 @@ [Definition] -failregex = \]\s*error\s*:\s*Warning:\s+Client '' supplied unknown user '\w+' accessing monit httpd$ - \]\s*error\s*:\s*Warning:\s+Client '' supplied wrong password for user '\w+' accessing monit httpd$ +failregex = ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '' supplied unknown user '\w+' accessing monit httpd$ + ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '' supplied wrong password for user '\w+' accessing monit httpd$ From bc10b64c69be90f9b5bc487d04c376b9432a40ac Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 27 Apr 2014 13:35:55 +0100 Subject: [PATCH 5/5] ENH: Match non "Bye Bye" for sshd locked accounts failregex --- ChangeLog | 1 + config/filter.d/sshd.conf | 2 +- fail2ban/tests/files/logs/sshd | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index bbe32054..40188554 100644 --- a/ChangeLog +++ b/ChangeLog @@ -29,6 +29,7 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger - Enhancements * Fail2ban-regex - add print-all-matched option. Closes gh-652 * Suppress fail2ban-client warnings for non-critical config options + * Match non "Bye Bye" disconnect messages for sshd locked account regex ver. 0.9.0 (2014/03/14) - beta ---------- diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index 059052fc..195744f2 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -30,7 +30,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro ^%(__prefix_line)sReceived disconnect from : 3: \S+: Auth fail$ ^%(__prefix_line)sUser .+ from not allowed because a group is listed in DenyGroups\s*$ ^%(__prefix_line)sUser .+ from not allowed because none of user's groups are listed in AllowGroups\s*$ - ^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked(?P=__prefix)(?:error: )?Received disconnect from : 11: Bye Bye \[preauth\]$ + ^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked(?P=__prefix)(?:error: )?Received disconnect from : 11: .+ \[preauth\]$ ^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\](?P=__prefix)(?:error: )?Connection closed by \[preauth\]$ ^(?P<__prefix>%(__prefix_line)s)Connection from port \d+(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$ diff --git a/fail2ban/tests/files/logs/sshd b/fail2ban/tests/files/logs/sshd index e2246cf8..b9d1b9b4 100644 --- a/fail2ban/tests/files/logs/sshd +++ b/fail2ban/tests/files/logs/sshd @@ -136,3 +136,10 @@ Jul 13 18:44:28 mdop sshd[4931]: Received disconnect from 89.24.13.192: 3: com.j Feb 12 04:09:18 localhost sshd[26713]: Connection from 115.249.163.77 port 51353 # failJSON: { "time": "2005-02-12T04:09:21", "match": true , "host": "115.249.163.77", "desc": "from gh-457" } Feb 12 04:09:21 localhost sshd[26713]: Disconnecting: Too many authentication failures for root [preauth] + +# failJSON: { "match": false } +Apr 27 13:02:04 host sshd[29116]: User root not allowed because account is locked +# failJSON: { "match": false } +Apr 27 13:02:04 host sshd[29116]: input_userauth_request: invalid user root [preauth] +# failJSON: { "time": "2005-04-27T13:02:04", "match": true , "host": "1.2.3.4", "desc": "No Bye-Bye" } +Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal Shutdown, Thank you for playing [preauth]