From 938297138b4e7c84567c1507bbdd478ac28c3f76 Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Mon, 27 Aug 2007 21:03:33 +0000 Subject: [PATCH 01/55] - Fixed named filter. Thanks to Yaroslav Halchenko git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_8@616 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 6 +++++- config/filter.d/named-refused.conf | 9 +++------ config/jail.conf | 12 ++++++------ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6d05c5c8..5ec92d0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,9 +4,13 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ============================================================= -Fail2Ban (version 0.8.1) 2007/08/14 +Fail2Ban (version 0.8.2) 2007/??/?? ============================================================= +ver. 0.8.2 (2007/??/??) - stable +---------- +- Fixed named filter. Thanks to Yaroslav Halchenko + ver. 0.8.1 (2007/08/14) - stable ---------- - Fixed vulnerability in sshd.conf. Thanks to Daniel B. Cid diff --git a/config/filter.d/named-refused.conf b/config/filter.d/named-refused.conf index d70e8732..74014644 100644 --- a/config/filter.d/named-refused.conf +++ b/config/filter.d/named-refused.conf @@ -9,10 +9,8 @@ [Definition] -# if you want to catch only login erros from specific daemons, use smth like -#_named_rcodes=(?:REFUSED|SERVFAIL) -# To catch all REFUSED queries only -_named_rcodes=REFUSED +# +# Daemon name _daemon=named # @@ -28,7 +26,6 @@ __line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)? # Notes.: regex to match the password failures messages in the logfile. # Values: TEXT # -failregex = %(__line_prefix)sunexpected RCODE \(%(_named_rcodes)s\) resolving '.*': #\S+$ - %(__line_prefix)sclient #\S+: query(?: \(cache\))? '.*' denied\s*$ +failregex = %(__line_prefix)sclient #\S+: query(?: \(cache\))? '.*' denied\s*$ diff --git a/config/jail.conf b/config/jail.conf index d61e7b6f..fbd10481 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -170,13 +170,13 @@ ignoreip = 168.192.0.1 # with bind9 installation. You will need something like this: # # logging { -# channel lame-servers_file { -# file "/var/log/named/lame-servers.log" versions 3 size 30m; +# channel security_file { +# file "/var/log/named/security.log" versions 3 size 30m; # severity dynamic; # print-time yes; # }; -# category lame-servers { -# lame-servers_file; +# category security { +# security_file; # }; # } # @@ -189,7 +189,7 @@ enabled = false filter = named-refused action = iptables-multiport[name=Named, port="domain,953", protocol=udp] sendmail-whois[name=Named, dest=you@mail.com] -logpath = /var/log/named/lame-servers.log +logpath = /var/log/named/security.log ignoreip = 168.192.0.1 # This jail blocks TCP traffic for DNS requests. @@ -200,6 +200,6 @@ enabled = false filter = named-refused action = iptables-multiport[name=Named, port="domain,953", protocol=tcp] sendmail-whois[name=Named, dest=you@mail.com] -logpath = /var/log/named/lame-servers.log +logpath = /var/log/named/security.log ignoreip = 168.192.0.1 From d885fc786e259b15865192f384e9db694e663c3c Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Mon, 27 Aug 2007 21:25:56 +0000 Subject: [PATCH 02/55] - Fixed wrong path for apache-auth in jail.conf. Thanks to Vincent Deffontaines git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_8@617 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 2 ++ config/jail.conf | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5ec92d0c..b5d4f549 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,8 @@ Fail2Ban (version 0.8.2) 2007/??/?? ver. 0.8.2 (2007/??/??) - stable ---------- - Fixed named filter. Thanks to Yaroslav Halchenko +- Fixed wrong path for apache-auth in jail.conf. Thanks to + Vincent Deffontaines ver. 0.8.1 (2007/08/14) - stable ---------- diff --git a/config/jail.conf b/config/jail.conf index fbd10481..57bc9839 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -89,8 +89,8 @@ logpath = /var/log/sshd.log enabled = false filter = apache-auth action = hostsdeny -logpath = /var/log/apache*/*access.log - /home/www/myhomepage/access.log +logpath = /var/log/apache*/*error.log + /home/www/myhomepage/error.log maxretry = 6 # The hosts.deny path can be defined with the "file" argument if it is From 9eda9e93c7838686249a60caeaeb3ed82dad4d9d Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Wed, 5 Sep 2007 20:43:47 +0000 Subject: [PATCH 03/55] - Fixed timezone bug with epoch date template. Thanks to Michael Hanselmann git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_8@618 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 2 ++ server/dateepoch.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b5d4f549..0d3063b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,8 @@ ver. 0.8.2 (2007/??/??) - stable - Fixed named filter. Thanks to Yaroslav Halchenko - Fixed wrong path for apache-auth in jail.conf. Thanks to Vincent Deffontaines +- Fixed timezone bug with epoch date template. Thanks to + Michael Hanselmann ver. 0.8.1 (2007/08/14) - stable ---------- diff --git a/server/dateepoch.py b/server/dateepoch.py index b6ee14d0..4d9eeb23 100644 --- a/server/dateepoch.py +++ b/server/dateepoch.py @@ -40,5 +40,5 @@ class DateEpoch(DateTemplate): dateMatch = self.matchDate(line) if dateMatch: # extract part of format which represents seconds since epoch - date = list(time.gmtime(float(dateMatch.group()))) + date = list(time.localtime(float(dateMatch.group()))) return date From 66063d2731ed1446f9132bfc805c55c769c95fa7 Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Wed, 12 Sep 2007 21:38:51 +0000 Subject: [PATCH 04/55] - Added "full line failregex" patch. Thanks to Yaroslav Halchenko. It will be possible to create stronger failregex against log injection git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_8@621 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 3 ++ MANIFEST | 2 + client/configparserinc.py | 97 +++++++++++++++++++++++++++++++++++++ client/configreader.py | 12 +++-- config/filter.d/common.conf | 41 ++++++++++++++++ config/filter.d/sshd.conf | 20 +++++--- fail2ban-regex | 10 ++-- server/filter.py | 34 ++++++++----- 8 files changed, 191 insertions(+), 28 deletions(-) create mode 100644 client/configparserinc.py create mode 100644 config/filter.d/common.conf diff --git a/CHANGELOG b/CHANGELOG index 0d3063b7..bca70982 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,9 @@ ver. 0.8.2 (2007/??/??) - stable Vincent Deffontaines - Fixed timezone bug with epoch date template. Thanks to Michael Hanselmann +- Added "full line failregex" patch. Thanks to Yaroslav + Halchenko. It will be possible to create stronger failregex + against log injection ver. 0.8.1 (2007/08/14) - stable ---------- diff --git a/MANIFEST b/MANIFEST index a4cc60ef..df9970c2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -7,6 +7,7 @@ fail2ban-server fail2ban-testcases fail2ban-regex client/configreader.py +client/configparserinc.py client/jailreader.py client/fail2banreader.py client/jailsreader.py @@ -60,6 +61,7 @@ common/__init__.py common/version.py common/protocol.py config/jail.conf +config/filter.d/common.conf config/filter.d/apache-auth.conf config/filter.d/apache-badbots.conf config/filter.d/apache-noscript.conf diff --git a/client/configparserinc.py b/client/configparserinc.py new file mode 100644 index 00000000..4e3e7575 --- /dev/null +++ b/client/configparserinc.py @@ -0,0 +1,97 @@ +# This file is part of Fail2Ban. +# +# Fail2Ban is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# Fail2Ban is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Fail2Ban; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# Author: Yaroslav Halchenko +# $Revision$ + +__author__ = 'Yaroslav Halhenko' +__revision__ = '$Revision: $' +__date__ = '$Date: $' +__copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko' +__license__ = 'GPL' + +from ConfigParser import SafeConfigParser +from ConfigParser import NoOptionError, NoSectionError + +class SafeConfigParserWithIncludes(SafeConfigParser): + """ + Class adds functionality to SafeConfigParser to handle included + other configuration files (or may be urls, whatever in the future) + + File should have section [includes] and only 2 options implemented + are 'files_before' and 'files_after' where files are listed 1 per + line. + + Example: + +[INCLUDES] +files_before = 1.conf + 3.conf + +files_after = 1.conf + + It is a simple implementation, so just basic care is taken about + recursion. Includes preserve right order, ie new files are + inserted to the list of read configs before original, and their + includes correspondingly so the list should follow the leaves of + the tree. + + I wasn't sure what would be the right way to implement generic (aka c++ + template) so we could base at any *configparser class... so I will + leave it for the future + + """ + + @staticmethod + def getIncludedFiles(filename, sectionName='INCLUDES', + defaults={}, seen=[]): + """ + Given 1 config filename returns list of included files + (recursively) with the original one as well + Simple loops are taken care about + """ + filenames = [] + #print "Opening file " + filename + d = defaults.copy() # so that we do not poison our defaults + parser = SafeConfigParser(defaults = d) + parser.read(filename) + newFiles = [ ('files_before', []), ('files_after', []) ] + if sectionName in parser.sections(): + for option_name, option_list in newFiles: + if option_name in parser.options(sectionName): + newFileNames = parser.get(sectionName, option_name) + for newFileName in newFileNames.split('\n'): + if newFileName in seen: continue + option_list += SafeConfigParserWithIncludes.\ + getIncludedFiles(newFileName, + defaults=defaults, + seen=seen + [filename]) + # combine lists + filenames = newFiles[0][1] + [filename] + newFiles[1][1] + #print "Includes list for " + filename + " is " + `filenames` + return filenames + + + def read(self, filenames): + fileNamesFull = [] + if not isinstance(filenames, list): + filenames = [ filenames ] + for filename in filenames: + fileNamesFull += SafeConfigParserWithIncludes.\ + getIncludedFiles(filename, defaults=self._defaults) + #print "Opening config files " + `fileNamesFull` + return SafeConfigParser.read(self, fileNamesFull) + diff --git a/client/configreader.py b/client/configreader.py index 1185bb65..355dce28 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -15,7 +15,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Author: Cyril Jaquier -# +# Modified by: Yaroslav Halchenko (SafeConfigParserWithIncludes) # $Revision$ __author__ = "Cyril Jaquier" @@ -25,18 +25,20 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import logging, os -from ConfigParser import SafeConfigParser +from configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError # Gets the instance of the logger. logSys = logging.getLogger("fail2ban.client.config") -class ConfigReader(SafeConfigParser): +class ConfigReader(SafeConfigParserWithIncludes): BASE_DIRECTORY = "/etc/fail2ban/" def __init__(self): - SafeConfigParser.__init__(self) + SafeConfigParserWithIncludes.__init__(self, + {'configpath' : \ + ConfigReader.BASE_DIRECTORY} ) self.__opts = None @staticmethod @@ -54,7 +56,7 @@ class ConfigReader(SafeConfigParser): bConf = basename + ".conf" bLocal = basename + ".local" if os.path.exists(bConf) or os.path.exists(bLocal): - SafeConfigParser.read(self, [bConf, bLocal]) + SafeConfigParserWithIncludes.read(self, [bConf, bLocal]) return True else: logSys.error(bConf + " and " + bLocal + " do not exist") diff --git a/config/filter.d/common.conf b/config/filter.d/common.conf new file mode 100644 index 00000000..40e3a360 --- /dev/null +++ b/config/filter.d/common.conf @@ -0,0 +1,41 @@ +# Generic configuration items (to be used as interpolations) in other +# filters or actions configurations +# +# Author: Yaroslav Halchenko +# +# $Revision: $ +# + +[INCLUDES] + +# Load customizations if any available +files_after = %(configpath)s/filter.d/common.local + + +[DEFAULT] + +# Daemon definition is to be specialized (if needed) in .conf file +_daemon = \S* + +# +# Shortcuts for easier comprehension of the failregex +# +# PID. +# EXAMPLES: [123] +__pid_re = (?:\[\d+\]) + +# Daemon name (with optional source_file:line or whatever) +# EXAMPLES: pam_rhosts_auth, [sshd], pop(pam_unix) +__daemon_re = [\[\(]?%(_daemon)s(?:\(\S+\))?[\]\)]?:? + +# Combinations of daemon name and PID +# EXAMPLES: sshd[31607], pop(pam_unix)[4920] +__daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:) + +# +# Common line prefixes (beginnings) which could be used in filters +# +# [hostname] [vserver tag] daemon_id spaces +# this can be optional (for instance if we match named native log files) +__prefix_line = \s*(?:\S+ )?(?:@vserver_\S+ )?%(__daemon_combs_re)s?\s* + diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index 1a601bd6..374a32f3 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -5,8 +5,17 @@ # $Revision$ # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +files_before = %(configpath)s/filter.d/common.conf + + [Definition] +_daemon = sshd + # Option: failregex # Notes.: regex to match the password failures messages in the logfile. The # host must be matched by a group named "host". The tag "" can @@ -14,12 +23,11 @@ # (?:::f{4,6}:)?(?P\S+) # Values: TEXT # -failregex = (?:error: PAM: )?Authentication failure for .* from \s*$ - Failed [-/\w]+ for .* from (?: port \d*)?(?: ssh\d*)?\s*$ - ROOT LOGIN REFUSED.* FROM \s*$ - [iI](?:llegal|nvalid) user .* from \s*$ - User .+ from not allowed because not listed in AllowUsers\s*$ - User .+ from not allowed because none of user's groups are listed in AllowGroups\s*$ +failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from \s*$ + ^%(__prefix_line)sFailed [-/\w]+ for .* from (?: port \d*)?(?: ssh\d*)?$ + ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM \s*$ + ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from \s*$ + ^%(__prefix_line)sUser \S+ from not allowed because not listed in AllowUsers$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/fail2ban-regex b/fail2ban-regex index 2f987e0c..294c6421 100755 --- a/fail2ban-regex +++ b/fail2ban-regex @@ -31,7 +31,7 @@ import getopt, sys, time, logging, os # fix for bug #343821 sys.path.insert(1, "/usr/share/fail2ban") -from ConfigParser import SafeConfigParser +from client.configparserinc import SafeConfigParserWithIncludes from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError from common.version import version from server.filter import Filter @@ -65,7 +65,9 @@ class RegexStat: class Fail2banRegex: test = None - + + CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"} + def __init__(self): self.__filter = Filter(None) self.__ignoreregex = list() @@ -135,7 +137,7 @@ class Fail2banRegex: def readIgnoreRegex(self, value): if os.path.isfile(value): - reader = SafeConfigParser() + reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS) try: reader.read(value) print "Use ignoreregex file : " + value @@ -164,7 +166,7 @@ class Fail2banRegex: def readRegex(self, value): if os.path.isfile(value): - reader = SafeConfigParser() + reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS) try: reader.read(value) print "Use regex file : " + value diff --git a/server/filter.py b/server/filter.py index 04a86d79..ddd44e78 100644 --- a/server/filter.py +++ b/server/filter.py @@ -383,7 +383,7 @@ class Filter(JailThread): logSys.error("Unable to get failures in " + filename) return False self.__setFilePos() - lastLine = None + lastTimeLine = None for line in self.__crtHandler: if not self._isActive(): # The jail has been stopped @@ -393,11 +393,18 @@ class Filter(JailThread): line = line.decode('utf-8') except UnicodeDecodeError: pass - if not self.dateDetector.matchTime(line): + timeMatch = self.dateDetector.matchTime(line) + if not timeMatch: # There is no valid time in this line continue - lastLine = line - for element in self.findFailure(line): + # Lets split into time part and log part of the line + timeLine = timeMatch.group() + # Lets leave the beginning in as well, so if there is no + # anchore at the beginning of the time regexp, we don't + # at least allow injection. Should be harmless otherwise + logLine = line[:timeMatch.start()] + line[timeMatch.end():] + lastTimeLine = timeLine + for element in self.findFailure(timeLine, logLine): ip = element[0] unixTime = element[1] if unixTime < MyTime.time()-self.__findTime: @@ -408,8 +415,8 @@ class Filter(JailThread): logSys.debug("Found "+ip) self.failManager.addFailure(FailTicket(ip, unixTime)) self.__lastPos[filename] = self.__getFilePos() - if lastLine: - self.__lastDate[filename] = self.dateDetector.getUnixTime(lastLine) + if lastTimeLine: + self.__lastDate[filename] = self.dateDetector.getUnixTime(lastTimeLine) self.__closeLogFile() return True @@ -428,27 +435,28 @@ class Filter(JailThread): return False ## - # Finds the failure in a line. + # Finds the failure in a line given split into time and log parts. # # Uses the failregex pattern to find it and timeregex in order # to find the logging time. # @return a dict with IP and timestamp. - def findFailure(self, line): + def findFailure(self, timeLine, logLine): failList = list() # Checks if we must ignore this line. - if self.ignoreLine(line): + if self.ignoreLine(logLine): # The ignoreregex matched. Return. return failList # Iterates over all the regular expressions. for failRegex in self.__failRegex: - failRegex.search(line) + failRegex.search(logLine) if failRegex.hasMatched(): # The failregex matched. - date = self.dateDetector.getUnixTime(line) + date = self.dateDetector.getUnixTime(timeLine) if date == None: - logSys.debug("Found a match but no valid date/time found " - + "for " + line + ". Please contact the " + logSys.debug("Found a match for '" + logLine +"' but no " + + "valid date/time found for '" + + timeLine + "'. Please contact the " + "author in order to get support for this " + "format") else: From c40534123ccf0875d5e09735a3846a4b773614ea Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Tue, 23 Oct 2007 22:06:31 +0000 Subject: [PATCH 05/55] - Fixed ipfw action script. Thanks to Nick Munger git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_8@623 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 1 + config/action.d/ipfw.conf | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bca70982..fa48e0b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ ver. 0.8.2 (2007/??/??) - stable - Added "full line failregex" patch. Thanks to Yaroslav Halchenko. It will be possible to create stronger failregex against log injection +- Fixed ipfw action script. Thanks to Nick Munger ver. 0.8.1 (2007/08/14) - stable ---------- diff --git a/config/action.d/ipfw.conf b/config/action.d/ipfw.conf index 0d5f1bbb..5305265e 100644 --- a/config/action.d/ipfw.conf +++ b/config/action.d/ipfw.conf @@ -37,7 +37,7 @@ actioncheck = #