diff --git a/.travis.yml b/.travis.yml index 2d091754..d693f975 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ # travis-ci.org definition for Fail2Ban build language: python python: - - "2.5" - "2.6" - "2.7" before_install: diff --git a/ChangeLog b/ChangeLog index ac97ebd5..e9b3d638 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,39 +4,85 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ================================================================================ -Fail2Ban (version 0.8.11.pre1) 2013/10/30 +Fail2Ban (version 0.8.12.dev) 2013/11/13 ================================================================================ -ver. 0.8.11 (2013/11/XXX) - loves-unittests and tight, DoS free, filter regexes +ver. 0.8.12 (2013/12/XX) - things-can-only-get-better ----------- -In light of CVE-2013-2178 that triggered our last release we have put a -significant effort into tightening all of the regexs of our filters to avoid -another similar vulnerability. All filters have been updated and some to -include more failure regexs supporting previously unbanned failures and -support for newer application versions too. There are test cases for most log -cases of failures now. - -As usual if you have other examples that demonstrate that a filter is -insufficient please give us an example log line on the github issue tracker -http://github.com/fail2ban/fail2ban/issues and NOT on a random blog in some -obscure corner of the Internet. - -During the tightening of the regexs to avoid DoS vulnerabilities there is the -possibility that we have inadvertently, despite our best intentions, -incorrectly allowed a failure to continue. We will fix this as quickly as -humanly possible. - -IMPORTANT incompatible changes: - Filter name changes: - * 'lighttpd-fastcgi' filter has been renamed to 'suhosin' - * 'sasl' has been renamed to 'postfix-sasl' - These will require changing in jail.{conf,local} if using these filters. - Exim filter has been split into an spam and a relay/auth filter. +- IMPORTANT incompatible changes: - Fixes: + - Rename firewall-cmd-direct-new to firewall-cmd-new to fit within jail name + name length. As per gh-395 + - allow for ",milliseconds" in the custom date format of proftpd.log + - allow for ", referer ..." in apache-* filter for apache error logs. + - allow for spaces at the beginning of kernel messages. Closes gh-448 + - recidive jail to block all protocols. Closes gh-440. Thanks Ioan Indreias + - smtps not a IANA standard and has been removed from Arch. Replaced with + 465. Thanks Stefan. Closes gh-447 + - mysqld-syslog-iptables rule was too long. Part of gh-447. + - add 'flushlogs' command to allow logrotation without clobbering logtarget + settings. Closes gh-458, Debian bug #697333, Redhat bug #891798. + - complain action - ensure where not matching other IPs in log sample. + Closes gh-467 + - Fix firewall-cmd actioncheck - patch from Adam Tkac. Redhat Bug #979622 + +- Enhancements: + - long names on jails documented based on iptables limit of 30 less + len("fail2ban-"). + - remove indentation of name and loglevel while logging to SYSLOG to + resolve syslog(-ng) parsing problems. Closes Debian bug #730202. + - added squid filter. Thanks Roman Gelfand. + - updated check_fail2ban to return performance data for all jails. + +- New Features: + + Daniel Black + * filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist. + Bas van den Dikkenberg & Steven Hiscocks + * filter.d/nsd.conf -- also amended Unix date template to match nsd format + +- Enhancements: + - loglines now also report "[PID]" after the name portion + + +ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes +----------- + +In light of CVE-2013-2178 that triggered our last release we have put +a significant effort into tightening all of the regexs of our filters +to avoid another similar vulnerability. All filters have been updated +and some to catch more login/authentication failures and to support +for newer application versions. There are test cases for most log +cases of failures now. + +As usual, if you have other examples that demonstrate that a filter is +insufficient, or if we have inadvertently introduced a regression, +please provide us with example log lines on the github issue tracker +http://github.com/fail2ban/fail2ban/issues and NOT on a random blog in +some obscure corner of the Internet. + +Many thanks to our contributors for this release Daniel Black, Yaroslav +Halchenko, Steven Hiscocks, Mark McKinstry, Andy Fragen, Orion Poplawski, +Alexander Dietrich, JP Espinosa, Jamyn Shanley, Beau Raines, François +Boulogne and others who have helped on IRC and mailing list, logged issues +and bug requests. + +- IMPORTANT incompatible changes: + Filter name changes: + * 'lighttpd-fastcgi' filter has been renamed to 'suhosin' + * 'sasl' has been renamed to 'postfix-sasl' + * 'exim' spam catching failregexes was split out into 'exim-spam' + These changes will require changing jail.{conf,local} if any of + those filters were used. + +- Fixes: + Jonathan Lanning + * filter.d/asterisk -- identified another regex for blocking. Also channel + ID is hex not decimal as noted in sample logs provided. Daniel Black & Marcel Dopita - * filter.d/apache-auth -- fixed and apache auth samples provide. closes #286 + * filter.d/apache-auth -- fixed and apache auth samples provide. Closes gh-286 Yaroslav Halchenko * filter.d/common.conf -- make colon after [daemon] optional. Closes gh-267 * filter.d/apache-common.conf -- support apache 2.4 more detailed error @@ -62,10 +108,12 @@ IMPORTANT incompatible changes: * filter.d/asterisk -- more regexes Daniel Black * action.d/hostsdeny -- NOTE: new dependancy 'ed'. Switched to use 'ed' across - all platforms to ensure permissions are the same before and after a ban - - closes gh-266. hostsdeny supports daemon_list now too. + all platforms to ensure permissions are the same before and after a ban. + Closes gh-266. hostsdeny supports daemon_list now too. * action.d/bsd-ipfw - action option unsed. Change blocktype to port unreach instead of deny for consistancy. + * filter.d/dovecot - added to support different dovecot failure + "..disallowed plaintext auth". Closes Debian bug #709324 * filter.d/roundcube-auth - timezone offset can be positive or negative * action.d/bsd-ipfw - action option unsed. Fixed to blocktype for consistency. default to port unreach instead of deny @@ -79,13 +127,15 @@ IMPORTANT incompatible changes: * filter.d/recidive -- support f2b syslog target and anchor regex at start * filter.d/mysqld-auth.conf - mysql can use syslog * filter.d/sshd - regex enhancements to support openssh-6.3. Closes Debian - bug #722970 + bug #722970. Thanks Colin Watson for the regex analysis. + * filter.d/wuftpd - regex enhancements to support pam and wuftpd. Closes + Debian bug #665925 Rolf Fokkens * action.d/dshield.conf and complain.conf -- reorder mailx arguments. https://bugzilla.redhat.com/show_bug.cgi?id=998020 John Doe (ache) * action.d/bsd-ipfw.conf - invert actionstop logic to make exist status 0. - closes gh-343. + Closes gh-343. JP Espinosa (Reviewed by O.Poplawski) * files/redhat-initd - rewritten to use stock init.d functions thus avoiding problems with getpid. Also $network and iptables moved @@ -95,8 +145,9 @@ IMPORTANT incompatible changes: - New Features: Edgar Hoch - * action.d/firewall-cmd-direct-new.conf - action for firewalld + * action.d/firewall-cmd-direct-new.conf - action for firewalld from https://bugzilla.redhat.com/show_bug.cgi?id=979622 + NOTE: requires firewalld-0.3.8+ Andy Fragen and Daniel Black * filter.d/osx-ipfw.conf - ipfw action for OSX based on random rule numbers. @@ -105,11 +156,13 @@ IMPORTANT incompatible changes: Daniel Black & ykimon * filter.d/3proxy.conf -- filter added * fail2ban-regex - now generates http://www.debuggex.com urls for debugging - regular expressions with the -D parameter. + regular expressions with the -D parameter. Daniel Black * filter.d/exim-spam.conf -- a splitout of exim's spam regexes with additions for greater control over filtering spam. * add date expression for apache-2.4 - milliseconds + * filter.d/nginx-http-auth -- filter added for http basic authentication + failures in nginx. Partially fulfills gh-405. Christophe Carles & Daniel Black * filter.d/perdition.conf -- filter added Mark McKinstry @@ -126,8 +179,8 @@ IMPORTANT incompatible changes: * reorder parsing of jail.conf, jail.d/*.conf, jail.local, jail.d/*.local and likewise for fail2ban.{conf|local|d/*.conf|d/*.local}. Closes gh-392 * jail.conf now has asterisk jail - no need for asterisk-tcp and - asterisk-udp. Users should replace existing jails with asterisk to - reduce duplicate parsing of the asterisk log file. + asterisk-udp. Users should replace existing jails with asterisk to + reduce duplicate parsing of the asterisk log file. * filter.d/{suhosin,pam-generic,gssftpd,sogo-auth,webmin}- regex anchor at start * filter.d/vsftpd - anchored regex at start. disable old pam format regex @@ -147,6 +200,9 @@ IMPORTANT incompatible changes: * filter.d/roundcube-auth.conf -- anchored version * date matching - for standard asctime formats prefer more detailed first (thus use year if available) + * files/gen_badbots was added and filter.d/apache-badbots.conf was + regenerated to get updated (although now still an old) list of + "bad" bots Alexander Dietrich * action.d/sendmail-common.conf -- added common sendmail settings file and made the sender display name configurable @@ -158,7 +214,7 @@ IMPORTANT incompatible changes: * filter.d/{courier{login,smtp},proftpd,sieve,wuftpd,xinetd} - General regex impovements Zurd - * filter.d/postfix - add filter for VRFY failures. closes gh-322. + * filter.d/postfix - add filter for VRFY failures. Closes gh-322. Orion Poplawski * fail2ban.d/ and jail.d/ directories are added to etc/fail2ban to facilitate their use diff --git a/DEVELOP b/DEVELOP index a8f8899f..3939fe47 100644 --- a/DEVELOP +++ b/DEVELOP @@ -289,15 +289,19 @@ TIP: Some applications log spaces at the end. If you are not sure add \s*$ as the end part of the regex. If your regex is not matching, http://www.debuggex.com/?flavor=python can help -to tune it: +to tune it. fail2ban-regex -D ... will present Debuggex URLs for the regexs +and sample log files that you pass into it. +In general use when using regex debuggers for generating fail2ban filters: * use regex from the ./fail2ban-regex output (to ensure all substitutions are -done) and replace with (?&.ipv4). Make sure that regex type set to -Python; -* for the test data put your log output with the time removed; -- when you have fixed the regex put it back into your filter file. +done) +* replace with (?&.ipv4) +* make sure that regex type set to Python +* for the test data put your log output with the date/time removed -Please spread the good word about debuggex - Serge Toarca is kindly continuing +When you have fixed the regex put it back into your filter file. + +Please spread the good word about Debuggex - Serge Toarca is kindly continuing its free availability to Open Source developers. Finishing up: @@ -327,7 +331,7 @@ failregex, while matching inserted text to the part, they have the ability to deny any host they choose. So the part must be anchored on text generated by the application, and -not the user, to a extent sufficient to prevent user inserting the entire text +not the user, to an extent sufficient to prevent user inserting the entire text matching this or any other failregex. Ideally filter regex should anchor at the beginning and at the end of log line. @@ -377,7 +381,7 @@ Note if we'd just had the expression: Then provided the user put a space in their command they would have never been banned. -2. Filter regex can match other user injected data +2. Unanchored regex can match other user injected data From the Apache vulnerability CVE-2013-2178 ( original ref: https://vndh.net/note:fail2ban-089-denial-service ). @@ -398,7 +402,82 @@ Now the log line will be: As this log line doesn't match other expressions hence it matches the above regex and blocks 192.168.33.1 as a denial of service from the HTTP requester. -3. Application generates two identical log messages with different meanings +3. Over greedy pattern matching + +From: https://github.com/fail2ban/fail2ban/pull/426 + +An example ssh log (simplified) + + Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser remoteuser + +As we assume username can include anything including spaces its prudent to put +.* here. The remote user can also exist as anything so lets not make assumptions again. + + failregex = ^%(__prefix_line)sFailed \S+ for .* from ( port \d*)?( ssh\d+)?(: ruser .*)?$ + +So this works. The problem is if the .* after remote user is injected by the +user to be 'from 1.2.3.4'. The resultant log line is. + + Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4 + +Testing with: + + fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .* from ( port \d*)?( ssh\d+)?(: ruser .*)?$' + +TIP: I've removed the bit that matches __prefix_line from the regex and log. + +Shows: + + 1) [1] ^ Failed \S+ for .* from ( port \d*)?( ssh\d+)?(: ruser .*)?$ + 1.2.3.4 Sun Sep 29 17:15:02 2013 + +It should of matched 127.0.0.1. So the first greedy part of the greedy regex +matched until the end of the string. The was no "from " so the regex +engine worked backwards from the end of the string until this was matched. + +The result was that 1.2.3.4 was matched, injected by the user, and the wrong IP +was banned. + +The solution here is to make the first .* non-greedy with .*?. Here it matches +as little as required and the fail2ban-regex tool shows the output: + + fail2ban-regex -v 'Sep 29 17:15:02 Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4' '^ Failed \S+ for .*? from ( port \d*)?( ssh\d+)?(: ruser .*)?$' + + 1) [1] ^ Failed \S+ for .*? from ( port \d*)?( ssh\d+)?(: ruser .*)?$ + 127.0.0.1 Sun Sep 29 17:15:02 2013 + +So the general case here is a log line that contains: + + (fixed_data_1)(fixed_data_2)(user_injectable_data) + +Where the regex that matches fixed_data_1 is gready and matches the entire +string, before moving backwards and user_injectable_data can match the entire +string. + +Another case: + +ref: https://www.debuggex.com/r/CtAbeKMa2sDBEfA2/0 + +A webserver logs the following without URL escaping: + + [error] 2865#0: *66647 user "xyz" was not found in "/file", client: 1.2.3.1, server: www.host.com, request: "GET ", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host", host: "www.myhost.com" + +regex: + + failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (?:password mismatch|was not found in ".*"), client: , server: \S+, request: "\S+ .+ HTTP/\d+\.\d+", host: "\S+" + +The .* matches to the end of the string. Finds that it can't continue to match +", client ... so it moves from the back and find that the user injected web URL: + + ", client: 3.2.1.1, server: fake.com, request: "GET exploited HTTP/3.3", host: "injected.host + +In this case there is a fixed host: "www.myhost.com" at the end so the solution +is to anchor the regex at the end with a $. + +If this wasn't the case then first .* needed to be made so it didn't capture +beyond . + +4. Application generates two identical log messages with different meanings If the application generates the following two messages under different circumstances: @@ -664,10 +743,14 @@ Releasing * https://github.com/fail2ban/fail2ban/issues?sort=updated&state=open * http://bugs.debian.org/cgi-bin/pkgreport.cgi?dist=unstable;package=fail2ban + * https://bugs.launchpad.net/ubuntu/+source/fail2ban * http://bugs.sabayon.org/buglist.cgi?quicksearch=net-analyzer%2Ffail2ban + * https://bugs.archlinux.org/?project=5&cat%5B%5D=33&string=fail2ban * https://bugs.gentoo.org/buglist.cgi?query_format=advanced&short_desc=fail2ban&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&short_desc_type=allwords * https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&component=fail2ban&classification=Red%20Hat&classification=Fedora * http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban + * https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban + * https://build.opensuse.org/package/requests/openSUSE:Factory/fail2ban # Make sure the tests pass @@ -691,23 +774,23 @@ Look for errors like: Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory - tar -C /tmp -jxf dist/fail2ban-0.8.11.tar.bz2 + tar -C /tmp -jxf dist/fail2ban-0.8.12.tar.bz2 # clean up current direcory - diff -rul --exclude \*.pyc . /tmp/fail2ban-0.8.11/ + diff -rul --exclude \*.pyc . /tmp/fail2ban-0.8.12/ # Only differences should be files that you don't want distributed. # Ensure the tests work from the tarball - cd /tmp/fail2ban-0.8.11/ && ./fail2ban-testcases-all + cd /tmp/fail2ban-0.8.12/ && ./fail2ban-testcases-all # Add/finalize the corresponding entry in the ChangeLog To generate a list of committers use e.g. - git shortlog -sn 0.8.10.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g' + git shortlog -sn 0.8.11.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g' Ensure the top of the ChangeLog has the right version and current date. @@ -726,18 +809,26 @@ Which indicates that testcases/files/logs/mysqld.log has been moved or is a dire # Provide a release sample to distributors + * Arch Linux: + https://www.archlinux.org/packages/community/any/fail2ban/ * Debian: Yaroslav Halchenko http://packages.qa.debian.org/f/fail2ban.html * FreeBSD: Christoph Theis theis@gmx.at>, Nick Hilliard http://svnweb.freebsd.org/ports/head/security/py-fail2ban/Makefile?view=markup + http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban * Fedora: Axel Thimm https://apps.fedoraproject.org/packages/fail2ban + http://pkgs.fedoraproject.org/cgit/fail2ban.git + https://admin.fedoraproject.org/pkgdb/acls/bugs/fail2ban * Gentoo: netmon@gentoo.org http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-x86/net-analyzer/fail2ban/metadata.xml?view=markup + https://bugs.gentoo.org/buglist.cgi?quicksearch=fail2ban * openSUSE: Stephan Kulow - https://build.opensuse.org/package/users?package=fail2ban&project=openSUSE%3AFactory + https://build.opensuse.org/package/show/openSUSE:Factory/fail2ban * Mac Ports: @Malbrouck on github (gh-49) https://trac.macports.org/browser/trunk/dports/security/fail2ban/Portfile + * Mageia: + https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban An potentially to the fail2ban-users directory. # Wait for feedback from distributors @@ -754,10 +845,15 @@ Which indicates that testcases/files/logs/mysqld.log has been moved or is a dire page: http://www.fail2ban.org/wiki/index.php/Commands * Update: - http://www.fail2ban.org/wiki/index.php/Downloads + http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_Versions&action=edit + + http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_News&action=edit + move old bits to: + http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_OldNews&action=edit + + http://www.fail2ban.org/wiki/index.php?title=Template:Fail2ban_Versions&action=edit http://www.fail2ban.org/wiki/index.php/ChangeLog http://www.fail2ban.org/wiki/index.php/Requirements (Check requirement) - http://www.fail2ban.org/wiki/index.php/Main_Page (Add to News) http://www.fail2ban.org/wiki/index.php/Features * See if any filters are upgraded: @@ -772,7 +868,7 @@ Post Release Add the following to the top of the ChangeLog -ver. 0.8.12 (2013/XX/XXX) - wanna-be-released +ver. 0.8.13 (2014/XX/XXX) - wanna-be-released ----------- - Fixes: diff --git a/MANIFEST b/MANIFEST index 43927a7a..a7fefd5c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -60,6 +60,7 @@ testcases/files/logs/exim testcases/files/logs/suhosin testcases/files/logs/mysqld-auth testcases/files/logs/named-refused +testcases/files/logs/nginx-http-auth testcases/files/logs/pam-generic testcases/files/logs/postfix testcases/files/logs/proftpd @@ -139,6 +140,7 @@ config/filter.d/apache-badbots.conf config/filter.d/apache-nohome.conf config/filter.d/apache-noscript.conf config/filter.d/apache-overflows.conf +config/filter.d/nginx-http-auth.conf config/filter.d/courierlogin.conf config/filter.d/couriersmtp.conf config/filter.d/cyrus-imap.conf @@ -239,3 +241,8 @@ files/bash-completion files/fail2ban-tmpfiles.conf files/fail2ban.service files/ipmasq-ZZZzzz_fail2ban.rul +files/gen_badbots +testcases/config/jail.conf +testcases/config/fail2ban.conf +testcases/config/filter.d/simple.conf +testcases/config/action.d/brokenaction.conf diff --git a/README.md b/README.md index 7c00233f..24ed11ff 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ / _|__ _(_) |_ ) |__ __ _ _ _ | _/ _` | | |/ /| '_ \/ _` | ' \ |_| \__,_|_|_/___|_.__/\__,_|_||_| - v0.8.11-pre1 2013/10/30 + v0.8.11 2013/11/13 ## Fail2Ban: ban hosts that cause multiple authentication errors diff --git a/THANKS b/THANKS index e70ca9c9..b9b86043 100644 --- a/THANKS +++ b/THANKS @@ -6,13 +6,17 @@ the project. If you have been left off, please let us know (preferably send a pull request on github with the "fix") and you will be added +Adam Tkac Adrien Clerc ache +ag4ve (Shawn) +Alasdair D. Campbell Amir Caspi Andrey G. Grozin Andy Fragen Arturo 'Buanzo' Busleiman Axel Thimm +Bas van den Dikkenberg Beau Raines Bill Heaton Carlos Alberto Lopez Perez @@ -33,7 +37,10 @@ Georgiy Mernov Guillaume Delvit Hanno 'Rince' Wagner Iain Lea +Jacques Lav!gnotte +Ioan Indreias Jonathan Kamens +Jonathan Lanning Jonathan Underwood Joël Bertrand JP Espinosa @@ -54,13 +61,16 @@ Michael Hanselmann Nick Munger Patrick Börjesson Raphaël Marichez +RealRancor René Berber Robert Edeker Rolf Fokkens +Roman Gelfand Russell Odom Sebastian Arcus Sireyessire silviogarbes +Stefan Tatschner Stephen Gildea Steven Hiscocks Tom Pike @@ -68,6 +78,7 @@ Tyler Vaclav Misek Vincent Deffontaines Yaroslav Halchenko +Winston Smith ykimon Yehuda Katz zugeschmiert diff --git a/client/beautifier.py b/client/beautifier.py index 8e690656..bc9e89b1 100644 --- a/client/beautifier.py +++ b/client/beautifier.py @@ -63,6 +63,8 @@ class Beautifier: msg = "Jail stopped" elif inC[0] == "add": msg = "Added jail " + response + elif inC[0] == "flushlogs": + msg = "logs: " + response elif inC[0:1] == ['status']: if len(inC) > 1: # Create IP list diff --git a/client/configreader.py b/client/configreader.py index 96aab5f3..470f029f 100644 --- a/client/configreader.py +++ b/client/configreader.py @@ -112,7 +112,7 @@ class ConfigReader(SafeConfigParserWithIncludes): except NoSectionError, e: # No "Definition" section or wrong basedir logSys.error(e) - values[option[1]] = option[2] + return False except NoOptionError: if not option[2] is None: logSys.warn("'%s' not defined in '%s'. Using default one: %r" diff --git a/client/jailreader.py b/client/jailreader.py index 7fbac423..6f78275b 100644 --- a/client/jailreader.py +++ b/client/jailreader.py @@ -35,7 +35,7 @@ logSys = logging.getLogger("fail2ban.client.config") class JailReader(ConfigReader): - actionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$") + actionCRE = re.compile("^([\w_.-]+)(?:\[(.*)\])?$") def __init__(self, name, force_enable=False, **kwargs): ConfigReader.__init__(self, **kwargs) @@ -54,7 +54,7 @@ class JailReader(ConfigReader): return ConfigReader.read(self, "jail") def isEnabled(self): - return self.__force_enable or self.__opts["enabled"] + return self.__force_enable or ( self.__opts and self.__opts["enabled"] ) @staticmethod def _glob(path): @@ -64,12 +64,10 @@ class JailReader(ConfigReader): """ pathList = [] for p in glob.glob(path): - if not os.path.exists(p): - logSys.warning("File %s doesn't even exist, thus cannot be monitored" % p) - elif not os.path.lexists(p): - logSys.warning("File %s is a dangling link, thus cannot be monitored" % p) - else: + if os.path.exists(p): pathList.append(p) + else: + logSys.warning("File %s is a dangling link, thus cannot be monitored" % p) return pathList def getOptions(self): @@ -86,18 +84,24 @@ class JailReader(ConfigReader): ["string", "filter", ""], ["string", "action", ""]] self.__opts = ConfigReader.getOptions(self, self.__name, opts) + if not self.__opts: + return False if self.isEnabled(): # Read filter - self.__filter = FilterReader(self.__opts["filter"], self.__name, - basedir=self.getBaseDir()) - ret = self.__filter.read() - if ret: - self.__filter.getOptions(self.__opts) + if self.__opts["filter"]: + self.__filter = FilterReader(self.__opts["filter"], self.__name, + basedir=self.getBaseDir()) + ret = self.__filter.read() + if ret: + self.__filter.getOptions(self.__opts) + else: + logSys.error("Unable to read the filter") + return False else: - logSys.error("Unable to read the filter") - return False - + self.__filter = None + logSys.warn("No filter set for jail %s" % self.__name) + # Read action for act in self.__opts["action"].split('\n'): try: @@ -165,7 +169,8 @@ class JailReader(ConfigReader): # Do not send a command if the rule is empty. if regex != '': stream.append(["set", self.__name, "addignoreregex", regex]) - stream.extend(self.__filter.convert()) + if self.__filter: + stream.extend(self.__filter.convert()) for action in self.__actions: stream.extend(action.convert()) stream.insert(0, ["add", self.__name, backend]) @@ -175,12 +180,16 @@ class JailReader(ConfigReader): def splitAction(action): m = JailReader.actionCRE.match(action) d = dict() - mgroups = m.groups() + try: + mgroups = m.groups() + except AttributeError: + raise ValueError("While reading action %s we should have got 1 or " + "2 groups. Got: 0" % action) if len(mgroups) == 2: action_name, action_opts = mgroups - elif len(mgroups) == 1: + elif len(mgroups) == 1: # pragma: nocover - unreachable - .* on second group always matches action_name, action_opts = mgroups[0], None - else: + else: # pragma: nocover - unreachable - regex only can capture 2 groups raise ValueError("While reading action %s we should have got up to " "2 groups. Got: %r" % (action, mgroups)) if not action_opts is None: diff --git a/client/jailsreader.py b/client/jailsreader.py index 00c63e3c..d32e6561 100644 --- a/client/jailsreader.py +++ b/client/jailsreader.py @@ -60,6 +60,7 @@ class JailsReader(ConfigReader): sections = [ section ] # Get the options of all jails. + parse_status = True for sec in sections: jail = JailReader(sec, basedir=self.getBaseDir(), force_enable=self.__force_enable) @@ -71,8 +72,8 @@ class JailsReader(ConfigReader): self.__jails.append(jail) else: logSys.error("Errors in jail %r. Skipping..." % sec) - return False - return True + parse_status = False + return parse_status def convert(self, allow_no_files=False): """Convert read before __opts and jails to the commands stream diff --git a/common/protocol.py b/common/protocol.py index 9309ce7f..278ccd53 100644 --- a/common/protocol.py +++ b/common/protocol.py @@ -43,6 +43,7 @@ protocol = [ ["get loglevel", "gets the logging level"], ["set logtarget ", "sets logging target to . Can be STDOUT, STDERR, SYSLOG or a file"], ["get logtarget", "gets logging target"], +["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."], ['', "JAIL CONTROL", ""], ["add ", "creates using "], ["start ", "starts the jail "], diff --git a/common/version.py b/common/version.py index a0cb94ea..eec5e794 100644 --- a/common/version.py +++ b/common/version.py @@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko" __license__ = "GPL" -version = "0.8.11.pre1" +version = "0.8.11.dev" diff --git a/config/action.d/apf.conf b/config/action.d/apf.conf index 9af3066d..f1d54dd2 100644 --- a/config/action.d/apf.conf +++ b/config/action.d/apf.conf @@ -41,3 +41,10 @@ actionban = apf --deny "banned by Fail2Ban " # Values: CMD # actionunban = apf --remove + +[Init] + +# Name used in APF configuration +# +name = default + diff --git a/config/action.d/blocklist_de.conf b/config/action.d/blocklist_de.conf new file mode 100644 index 00000000..d4170cab --- /dev/null +++ b/config/action.d/blocklist_de.conf @@ -0,0 +1,86 @@ +# Fail2Ban configuration file +# +# Author: Steven Hiscocks +# +# + +# Action to report IP address to blocklist.de +# Blocklist.de must be signed up to at www.blocklist.de +# Once registered, one or more servers can be added. +# This action requires the server 'email address' and the assoicate apikey. +# +# From blocklist.de: +# www.blocklist.de is a free and voluntary service provided by a +# Fraud/Abuse-specialist, whose servers are often attacked on SSH-, +# Mail-Login-, FTP-, Webserver- and other services. +# The mission is to report all attacks to the abuse deparments of the +# infected PCs/servers to ensure that the responsible provider can inform +# the customer about the infection and disable them +# +# IMPORTANT: +# +# Reporting an IP of abuse is a serious complaint. Make sure that it is +# serious. Fail2ban developers and network owners recommend you only use this +# action for: +# * The recidive where the IP has been banned multiple times +# * Where maxretry has been set quite high, beyond the normal user typing +# password incorrectly. +# * For filters that have a low likelyhood of receiving human errors +# + +[Definition] + +# Option: actionstart +# Notes.: command executed once at the start of Fail2Ban. +# Values: CMD +# +actionstart = + +# Option: actionstop +# Notes.: command executed once at the end of Fail2Ban +# Values: CMD +# +actionstop = + +# Option: actioncheck +# Notes.: command executed once before each actionban command +# Values: CMD +# +actioncheck = + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = curl --fail --data-urlencode 'server=' --data 'apikey=' --data 'service=' --data 'ip=' --data-urlencode 'logs=' --data 'format=text' --user-agent "fail2ban v0.8.12" "https://www.blocklist.de/en/httpreports.html" + +# Option: actionunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionunban = + +[Init] + +# Option: email +# Notes server email address, as per blocklise.de account +# Values: STRING Default: None +# +#email = + +# Option: apikey +# Notes your user blocklist.de user account apikey +# Values: STRING Default: None +# +#apikey = + +# Option: service +# Notes service name you are reporting on, typically aligns with filter name +# see http://www.blocklist.de/en/httpreports.html for full list +# Values: STRING Default: None +# +#service = diff --git a/config/action.d/complain.conf b/config/action.d/complain.conf index ad14a87e..62331f19 100644 --- a/config/action.d/complain.conf +++ b/config/action.d/complain.conf @@ -58,7 +58,7 @@ actioncheck = actionban = ADDRESSES=`whois | perl -e 'while () { next if /^changed|@(ripe|apnic)\.net/io; $m += (/abuse|trouble:|report|spam|security/io?3:0); if (/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)/io) { while (s/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)//io) { if ($m) { $a{lc($1)}=$m } else { $b{lc($1)}=$m } } $m=0 } else { $m && --$m } } if (%%a) {print join(",",keys(%%a))} else {print join(",",keys(%%b))}'` IP= if [ ! -z "$ADDRESSES" ]; then - (printf %%b "\n"; date '+Note: Local timezone is %%z (%%Z)'; grep '' ) | "Abuse from " $ADDRESSES + (printf %%b "\n"; date '+Note: Local timezone is %%z (%%Z)'; grep -E '(^|[^0-9])([^0-9]|$)' ) | "Abuse from " $ADDRESSES fi # Option: actionunban diff --git a/config/action.d/firewall-cmd-direct-new.conf b/config/action.d/firewallcmd-new.conf similarity index 64% rename from config/action.d/firewall-cmd-direct-new.conf rename to config/action.d/firewallcmd-new.conf index ac06aa57..bae72ca2 100644 --- a/config/action.d/firewall-cmd-direct-new.conf +++ b/config/action.d/firewallcmd-new.conf @@ -1,9 +1,6 @@ # Fail2Ban configuration file # -# Author: Edgar Hoch -# Copied from iptables-new.conf and modified for use with firewalld by Edgar Hoch. -# It uses "firewall-cmd" instead of "iptables". -# firewall-cmd is based on the command of version firewalld-0.3.4-1.fc19. +# Because of the --remove-rules in stop this action requires firewalld-0.3.8+ [INCLUDES] @@ -15,18 +12,11 @@ actionstart = firewall-cmd --direct --add-chain ipv4 filter fail2ban- firewall-cmd --direct --add-rule ipv4 filter fail2ban- 1000 -j RETURN firewall-cmd --direct --add-rule ipv4 filter 0 -m state --state NEW -p --dport -j fail2ban- -# The following rule does not work, because firewalld keeps its own database of firewall rules. -# firewall-cmd --direct --passthrough ipv4 -F fail2ban- -# The better rule would be the following, but firewall-cmd has not implemented this command with firewalld-0.3.3-2.fc19 . -# firewall-cmd --direct --flush-chain ipv4 filter fail2ban- -# The following is a workaround using a loop to implement the --flush-chain command. -# https://fedorahosted.org/firewalld/ticket/10 - actionstop = firewall-cmd --direct --remove-rule ipv4 filter 0 -m state --state NEW -p --dport -j fail2ban- - ( IFS='|' ; for r in $( firewall-cmd --direct --get-rules ipv4 filter fail2ban- | tr '\n' '|' ) ; do eval firewall-cmd --direct --remove-rule ipv4 filter fail2ban- $r ; done ) + firewall-cmd --direct --remove-rules ipv4 filter fail2ban- firewall-cmd --direct --remove-chain ipv4 filter fail2ban- -actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q 'fail2ban-[ \t]' +actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '^fail2ban-$' actionban = firewall-cmd --direct --add-rule ipv4 filter fail2ban- 0 -s -j @@ -56,3 +46,27 @@ protocol = tcp # Values: [ STRING ] # chain = INPUT_direct + +# DEV NOTES: +# +# Author: Edgar Hoch +# Copied from iptables-new.conf and modified for use with firewalld by Edgar Hoch. +# It uses "firewall-cmd" instead of "iptables". +# +# Output: +# +# $ firewall-cmd --direct --add-chain ipv4 filter fail2ban-name +# success +# $ firewall-cmd --direct --add-rule ipv4 filter fail2ban-name 1000 -j RETURN +# success +# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp --dport 22 -j fail2ban-name +# success +# $ firewall-cmd --direct --get-chains ipv4 filter +# fail2ban-name +# $ firewall-cmd --direct --get-chains ipv4 filter | od -h +# 0000000 6166 6c69 6232 6e61 6e2d 6d61 0a65 +# $ firewall-cmd --direct --get-chains ipv4 filter | grep -Eq 'fail2ban-name( |$)' ; echo $? +# 0 +# $ firewall-cmd -V +# 0.3.8 + diff --git a/config/action.d/ipfw.conf b/config/action.d/ipfw.conf index 09045815..37625209 100644 --- a/config/action.d/ipfw.conf +++ b/config/action.d/ipfw.conf @@ -43,7 +43,7 @@ actionban = ipfw add tcp from to # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = ipfw delete `ipfw list | grep -i | awk '{print $1;}'` +actionunban = ipfw delete `ipfw list | grep -i "[^0-9][^0-9]" | awk '{print $1;}'` [Init] diff --git a/config/action.d/mail-whois-lines.conf b/config/action.d/mail-whois-lines.conf index 758c4eff..aa7d0950 100644 --- a/config/action.d/mail-whois-lines.conf +++ b/config/action.d/mail-whois-lines.conf @@ -39,10 +39,10 @@ actioncheck = actionban = printf %%b "Hi,\n The IP has just been banned by Fail2Ban after attempts against .\n\n - Here are more information about :\n - `whois `\n\n + Here is more information about :\n + `whois || echo missing whois program`\n\n Lines containing IP: in \n - `grep '\<\>' `\n\n + `grep '[^0-9][^0-9]' `\n\n Regards,\n Fail2Ban"|mail -s "[Fail2Ban] : banned from `uname -n`" diff --git a/config/action.d/mail-whois.conf b/config/action.d/mail-whois.conf index fa133ab3..e4c8450e 100644 --- a/config/action.d/mail-whois.conf +++ b/config/action.d/mail-whois.conf @@ -39,8 +39,8 @@ actioncheck = actionban = printf %%b "Hi,\n The IP has just been banned by Fail2Ban after attempts against .\n\n - Here are more information about :\n - `whois `\n + Here is more information about :\n + `whois || echo missing whois program`\n Regards,\n Fail2Ban"|mail -s "[Fail2Ban] : banned from `uname -n`" diff --git a/config/action.d/sendmail-whois-lines.conf b/config/action.d/sendmail-whois-lines.conf index 5a331e24..e97868bd 100644 --- a/config/action.d/sendmail-whois-lines.conf +++ b/config/action.d/sendmail-whois-lines.conf @@ -55,10 +55,10 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Hi,\n The IP has just been banned by Fail2Ban after attempts against .\n\n - Here are more information about :\n - `/usr/bin/whois `\n\n + Here is more information about :\n + `/usr/bin/whois || echo missing whois program`\n\n Lines containing IP: in \n - `grep '\<\>' `\n\n + `grep '[^0-9][^0-9]' `\n\n Regards,\n Fail2Ban" | /usr/sbin/sendmail -f diff --git a/config/action.d/sendmail-whois.conf b/config/action.d/sendmail-whois.conf index a65f9875..e428c44d 100644 --- a/config/action.d/sendmail-whois.conf +++ b/config/action.d/sendmail-whois.conf @@ -55,8 +55,8 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Hi,\n The IP has just been banned by Fail2Ban after attempts against .\n\n - Here are more information about :\n - `/usr/bin/whois `\n + Here is more information about :\n + `/usr/bin/whois || echo missing whois program`\n Regards,\n Fail2Ban" | /usr/sbin/sendmail -f diff --git a/config/filter.d/apache-auth.conf b/config/filter.d/apache-auth.conf index 3df91c15..f4213487 100644 --- a/config/filter.d/apache-auth.conf +++ b/config/filter.d/apache-auth.conf @@ -10,19 +10,19 @@ before = apache-common.conf [Definition] -failregex = ^%(_apache_error_client)s (AH01797: )?client denied by server configuration: (uri )?\S*\s*$ - ^%(_apache_error_client)s (AH01617: )?user .* authentication failure for "\S*": Password Mismatch$ - ^%(_apache_error_client)s (AH01618: )?user .* not found(: )?\S*\s*$ - ^%(_apache_error_client)s (AH01614: )?client used wrong authentication scheme: \S*\s*$ +failregex = ^%(_apache_error_client)s (AH01797: )?client denied by server configuration: (uri )?\S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01617: )?user .*? authentication failure for "\S*": Password Mismatch(, referer: \S+)?$ + ^%(_apache_error_client)s (AH01618: )?user .*? not found(: )?\S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01614: )?client used wrong authentication scheme: \S*(, referer: \S+)?\s*$ ^%(_apache_error_client)s (AH\d+: )?Authorization of user \S+ to access \S* failed, reason: .*$ - ^%(_apache_error_client)s (AH0179[24]: )?(Digest: )?user .*: password mismatch: \S*\s*$ - ^%(_apache_error_client)s (AH0179[01]: |Digest: )user `.*' in realm `.+' (not found|denied by provider): \S*\s*$ - ^%(_apache_error_client)s (AH01631: )?user .*: authorization failure for "\S*":\s*$ - ^%(_apache_error_client)s (AH01775: )?(Digest: )?invalid nonce .* received - length is not \S+\s*$ - ^%(_apache_error_client)s (AH01788: )?(Digest: )?realm mismatch - got `.*' but expected `.+'\s*$ - ^%(_apache_error_client)s (AH01789: )?(Digest: )?unknown algorithm `.*' received: \S*\s*$ - ^%(_apache_error_client)s (AH01793: )?invalid qop `.*' received: \S*\s*$ - ^%(_apache_error_client)s (AH01777: )?(Digest: )?invalid nonce .* received - user attempted time travel\s*$ + ^%(_apache_error_client)s (AH0179[24]: )?(Digest: )?user .*?: password mismatch: \S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH0179[01]: |Digest: )user `.*?' in realm `.+' (not found|denied by provider): \S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01631: )?user .*?: authorization failure for "\S*":(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01775: )?(Digest: )?invalid nonce .* received - length is not \S+(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01788: )?(Digest: )?realm mismatch - got `.*?' but expected `.+'(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01789: )?(Digest: )?unknown algorithm `.*?' received: \S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01793: )?invalid qop `.*?' received: \S*(, referer: \S+)?\s*$ + ^%(_apache_error_client)s (AH01777: )?(Digest: )?invalid nonce .*? received - user attempted time travel(, referer: \S+)?\s*$ ignoreregex = @@ -50,5 +50,7 @@ ignoreregex = # ^%(_apache_error_client)s (AH01779: )?user .*: one-time-nonce mismatch - sending new nonce\s*$ # ^%(_apache_error_client)s (AH02486: )?realm mismatch - got `.*' but no realm specified\s*$ # +# referer is always in error log messages if it exists added as per the log_error_core function in server/log.c +# # Author: Cyril Jaquier # Major edits by Daniel Black diff --git a/config/filter.d/apache-badbots.conf b/config/filter.d/apache-badbots.conf index 9ee44c69..b2ac9626 100644 --- a/config/filter.d/apache-badbots.conf +++ b/config/filter.d/apache-badbots.conf @@ -3,12 +3,12 @@ # Regexp to catch known spambots and software alike. Please verify # that it is your intent to block IPs which were driven by # above mentioned bots. - + [Definition] badbotscustom = EmailCollector|WebEMailExtrac|TrackBack/1\.02|sogou music spider -badbots = atSpider/1\.0|autoemailspider|China Local Browse 2\.6|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailWolf 1\.00|ESurf15a 15|ExtractorPro|Franklin Locator 1\.8|FSurf15a 01|Full Web Bot 0416B|Full Web Bot 0516B|Full Web Bot 2816B|Industry Program 1\.0\.x|ISC Systems iRc Search 2\.1|IUPUI Research Bot v 1\.9a|LARBIN-EXPERIMENTAL \(efp@gmx\.net\)|LetsCrawl\.com/1\.0 +http\://letscrawl\.com/|Lincoln State Web Browser|LWP\:\:Simple/5\.803|Mac Finder 1\.0\.xx|MFC Foundation Class Library 4\.0|Microsoft URL Control - 6\.00\.8xxx|Missauga Locate 1\.0\.0|Missigua Locator 1\.9|Missouri College Browse|Mizzu Labs 2\.2|Mo College 1\.9|Mozilla/2\.0 \(compatible; NEWT ActiveX; Win32\)|Mozilla/3\.0 \(compatible; Indy Library\)|Mozilla/4\.0 \(compatible; Advanced Email Extractor v2\.xx\)|Mozilla/4\.0 \(compatible; Iplexx Spider/1\.0 http\://www\.iplexx\.at\)|Mozilla/4\.0 \(compatible; MSIE 5\.0; Windows NT; DigExt; DTS Agent|Mozilla/4\.0 efp@gmx\.net|Mozilla/5\.0 \(Version\: xxxx Type\:xx\)|MVAClient|NASA Search 1\.0|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|Port Huron Labs|Production Bot 0116B|Production Bot 2016B|Production Bot DOT 3016B|Program Shareware 1\.0\.2|PSurf15a 11|PSurf15a 51|PSurf15a VA|psycheclone|RSurf15a 41|RSurf15a 51|RSurf15a 81|searchbot admin@google\.com|sogou spider|sohu agent|SSurf15a 11 |TSurf15a 11|Under the Rainbow 2\.2|User-Agent\: Mozilla/4\.0 \(compatible; MSIE 6\.0; Windows NT 5\.1\)|WebVulnCrawl\.blogspot\.com/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00 +badbots = Atomic_Email_Hunter/4\.0|atSpider/1\.0|autoemailspider|bwh3_user_agent|China Local Browse 2\.6|ContactBot/0\.2|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailSpider|EmailWolf 1\.00|ESurf15a 15|ExtractorPro|Franklin Locator 1\.8|FSurf15a 01|Full Web Bot 0416B|Full Web Bot 0516B|Full Web Bot 2816B|Guestbook Auto Submitter|Industry Program 1\.0\.x|ISC Systems iRc Search 2\.1|IUPUI Research Bot v 1\.9a|LARBIN-EXPERIMENTAL \(efp@gmx\.net\)|LetsCrawl\.com/1\.0 +http\://letscrawl\.com/|Lincoln State Web Browser|LMQueueBot/0\.2|LWP\:\:Simple/5\.803|Mac Finder 1\.0\.xx|MFC Foundation Class Library 4\.0|Microsoft URL Control - 6\.00\.8xxx|Missauga Locate 1\.0\.0|Missigua Locator 1\.9|Missouri College Browse|Mizzu Labs 2\.2|Mo College 1\.9|MVAClient|Mozilla/2\.0 \(compatible; NEWT ActiveX; Win32\)|Mozilla/3\.0 \(compatible; Indy Library\)|Mozilla/3\.0 \(compatible; scan4mail \(advanced version\) http\://www\.peterspages\.net/?scan4mail\)|Mozilla/4\.0 \(compatible; Advanced Email Extractor v2\.xx\)|Mozilla/4\.0 \(compatible; Iplexx Spider/1\.0 http\://www\.iplexx\.at\)|Mozilla/4\.0 \(compatible; MSIE 5\.0; Windows NT; DigExt; DTS Agent|Mozilla/4\.0 efp@gmx\.net|Mozilla/5\.0 \(Version\: xxxx Type\:xx\)|NameOfAgent \(CMS Spider\)|NASA Search 1\.0|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|Port Huron Labs|Production Bot 0116B|Production Bot 2016B|Production Bot DOT 3016B|Program Shareware 1\.0\.2|PSurf15a 11|PSurf15a 51|PSurf15a VA|psycheclone|RSurf15a 41|RSurf15a 51|RSurf15a 81|searchbot admin@google\.com|ShablastBot 1\.0|snap\.com beta crawler v0|Snapbot/1\.0|Snapbot/1\.0 \(Snap Shots, +http\://www\.snap\.com\)|sogou develop spider|Sogou Orion spider/3\.0\(+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sogou spider|Sogou web spider/3\.0\(+http\://www\.sogou\.com/docs/help/webmasters\.htm#07\)|sohu agent|SSurf15a 11 |TSurf15a 11|Under the Rainbow 2\.2|User-Agent\: Mozilla/4\.0 \(compatible; MSIE 6\.0; Windows NT 5\.1\)|VadixBot|WebVulnCrawl\.unknown/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00 failregex = ^ -.*"(GET|POST).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$ @@ -16,6 +16,6 @@ ignoreregex = # DEV Notes: # List of bad bots fetched from http://www.user-agents.org -# Generated on Sun Feb 11 01:09:15 EST 2007 by ./badbots.sh +# Generated on Thu Nov 7 14:23:35 PST 2013 by files/gen_badbots. # # Author: Yaroslav Halchenko diff --git a/config/filter.d/apache-noscript.conf b/config/filter.d/apache-noscript.conf index 4ecf349a..7ea257b2 100644 --- a/config/filter.d/apache-noscript.conf +++ b/config/filter.d/apache-noscript.conf @@ -9,10 +9,16 @@ before = apache-common.conf [Definition] -failregex = ^%(_apache_error_client)s (File does not exist|script not found or unable to stat): /\S*(\.php|\.asp|\.exe|\.pl)\s*$ - ^%(_apache_error_client)s script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$ +failregex = ^%(_apache_error_client)s ((AH001(28|30): )?File does not exist|(AH01264: )?script not found or unable to stat): /\S*(\.php|\.asp|\.exe|\.pl)(, referer: \S+)?\s*$ + ^%(_apache_error_client)s script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat(, referer: \S+)?\s*$ ignoreregex = +# DEV Notes: +# +# https://wiki.apache.org/httpd/ListOfErrors for apache error IDs +# +# Second regex, script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$ is Before http-2.2 +# # Author: Cyril Jaquier diff --git a/config/filter.d/apache-overflows.conf b/config/filter.d/apache-overflows.conf index de1c770d..74e44b8e 100644 --- a/config/filter.d/apache-overflows.conf +++ b/config/filter.d/apache-overflows.conf @@ -8,8 +8,29 @@ before = apache-common.conf [Definition] -failregex = ^%(_apache_error_client)s (Invalid (method|URI) in request|request failed: URI too long|erroneous characters after protocol string) +failregex = ^%(_apache_error_client)s ((AH0013[456]: )?Invalid (method|URI) in request .*( - possible attempt to establish SSL connection on non-SSL port)?|(AH00565: )?request failed: URI too long \(longer than \d+\)|request failed: erroneous characters after protocol string: .*|AH00566: request failed: invalid characters in URI)(, referer: \S+)?$ ignoreregex = +# DEV Notes: +# +# fgrep -r 'URI too long' httpd-2.* +# httpd-2.2.25/server/protocol.c: "request failed: URI too long (longer than %d)", r->server->limit_req_line); +# httpd-2.4.4/server/protocol.c: "request failed: URI too long (longer than %d)", +# +# fgrep -r 'in request' ../httpd-2.* | fgrep Invalid +# httpd-2.2.25/server/core.c: "Invalid URI in request %s", r->the_request); +# httpd-2.2.25/server/core.c: "Invalid method in request %s", r->the_request); +# httpd-2.2.25/docs/manual/rewrite/flags.html.fr:avertissements 'Invalid URI in request'. +# httpd-2.4.4/server/core.c: "Invalid URI in request %s", r->the_request); +# httpd-2.4.4/server/core.c: "Invalid method in request %s - possible attempt to establish SSL connection on non-SSL port", r->the_request); +# httpd-2.4.4/server/core.c: "Invalid method in request %s", r->the_request); +# +# fgrep -r 'invalid characters in URI' httpd-2.* +# httpd-2.4.4/server/protocol.c: "request failed: invalid characters in URI"); +# +# http://svn.apache.org/viewvc/httpd/httpd/trunk/server/core.c?r1=739382&r2=739620&pathrev=739620 +# ...possible attempt to establish SSL connection on non-SSL port +# +# https://wiki.apache.org/httpd/ListOfErrors # Author: Tim Connors diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index 35906d11..3c1a97df 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -6,15 +6,9 @@ __pid_re = (?:\[\d+\]) # All Asterisk log messages begin like this: -log_prefix= \[\]\s*(?:NOTICE|SECURITY)%(__pid_re)s:?(?:\[\S+\d*\])? \S+:\d* +log_prefix= \[\]\s*(?:NOTICE|SECURITY)%(__pid_re)s:?(?:\[C-[\da-f]*\])? \S+:\d* -failregex = ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - Wrong password$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - No matching peer found$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - Username/auth name mismatch$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - Device does not match ACL$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - Peer is not supposed to register$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - ACL error \(permit/deny\)$ - ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - Not a local domain$ +failregex = ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' - (Wrong password|No matching peer found|Username/auth name mismatch|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$ ^%(log_prefix)s Call from '[^']*' \(:\d+\) to extension '\d+' rejected because extension not found in context 'default'\.$ ^%(log_prefix)s Host failed to authenticate as '[^']*'$ ^%(log_prefix)s No registration for peer '[^']*' \(from \)$ @@ -22,6 +16,7 @@ failregex = ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' ^%(log_prefix)s Failed to authenticate (user|device) [^@]+@\S*$ ^%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*>;tag=\w+\S*$ ^%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d+",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P//\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?$ + ^\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? Ext\. s: "Rejecting unknown SIP connection from "$ ignoreregex = diff --git a/config/filter.d/common.conf b/config/filter.d/common.conf index b992e4b8..ae8e8b7b 100644 --- a/config/filter.d/common.conf +++ b/config/filter.d/common.conf @@ -34,7 +34,7 @@ __daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_r # Some messages have a kernel prefix with a timestamp # EXAMPLES: kernel: [769570.846956] -__kernel_prefix = kernel: \[\d+\.\d+\] +__kernel_prefix = kernel: \[ *\d+\.\d+\] __hostname = \S+ diff --git a/config/filter.d/dovecot.conf b/config/filter.d/dovecot.conf index 2caa04b3..a51ce259 100644 --- a/config/filter.d/dovecot.conf +++ b/config/filter.d/dovecot.conf @@ -10,7 +10,7 @@ before = common.conf _daemon = (auth|dovecot(-auth)?|auth-worker) failregex = ^%(__prefix_line)s(pam_unix(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=(\s+user=\S*)?\s*$ - ^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use disabled \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=, lip=(\d{1,3}\.){3}\d{1,3}(, session=<\w+>)?(, TLS( handshaking)?(: Disconnected)?)?\s*$ + ^%(__prefix_line)s(pop3|imap)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity)? \(((no auth attempts|auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=, lip=(\d{1,3}\.){3}\d{1,3}(, session=<\w+>)?(, TLS( handshaking)?(: Disconnected)?)?\s*$ ^%(__prefix_line)s(Info|dovecot: auth\(default\)): pam\(\S+,\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$ ignoreregex = diff --git a/config/filter.d/dropbear.conf b/config/filter.d/dropbear.conf index 54d8166b..288b0882 100644 --- a/config/filter.d/dropbear.conf +++ b/config/filter.d/dropbear.conf @@ -23,8 +23,8 @@ before = common.conf _daemon = dropbear -failregex = ^%(__prefix_line)s[Ll]ogin attempt for nonexistent user ('.*' )?from :.*$ - ^%(__prefix_line)s[Bb]ad (PAM )?password attempt for .+ from .*$ +failregex = ^%(__prefix_line)s[Ll]ogin attempt for nonexistent user ('.*' )?from :\d+$ + ^%(__prefix_line)s[Bb]ad (PAM )?password attempt for .+ from (:\d+)?$ ^%(__prefix_line)s[Ee]xit before auth \(user '.+', \d+ fails\): Max auth tries reached - user '.+' from :\d+\s*$ ignoreregex = @@ -37,5 +37,12 @@ ignoreregex = # # The second last failregex line we need to match with the modified dropbear. # +# For the second regex the following apply: +# +# http://www.netmite.com/android/mydroid/external/dropbear/svr-authpam.c +# http://svn.dd-wrt.com/changeset/16642#file64 +# +# http://svn.dd-wrt.com/changeset/16642/src/router/dropbear/svr-authpasswd.c +# # Author: Francis Russell # Zak B. Elep diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf index 5f786594..66743390 100644 --- a/config/filter.d/exim.conf +++ b/config/filter.d/exim.conf @@ -16,7 +16,7 @@ before = exim-common.conf failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$ ^%(pid)s (plain|login) authenticator failed for (\S+ )?\(\S+\) \[\]: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$ ^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$ - ^%(pid)s SMTP protocol synchronization error \(.*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$ + ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$ ^%(pid)s SMTP call from \S+ \[\](:\d+)? (I=\[\S+\]:\d+ )?dropped: too many nonmail commands \(last was "\S+"\)\s*$ ignoreregex = @@ -24,5 +24,9 @@ ignoreregex = # DEV Notes: # The %(host_info) defination contains a match # +# SMTP protocol synchronization error \([^)]*\) <- This needs to be non-greedy +# to void capture beyond ")" to avoid a DoS Injection vulnerabilty as input= is +# user injectable data. +# # Author: Cyril Jaquier # Daniel Black (rewrote with strong regexs) diff --git a/config/filter.d/nginx-http-auth.conf b/config/filter.d/nginx-http-auth.conf new file mode 100644 index 00000000..79dda30b --- /dev/null +++ b/config/filter.d/nginx-http-auth.conf @@ -0,0 +1,15 @@ +# fail2ban filter configuration for nginx + + +[Definition] + + +failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: , server: \S+, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"\s*$ + +ignoreregex = + +# DEV NOTES: +# Based on samples in https://github.com/fail2ban/fail2ban/pull/43/files +# Extensive search of all nginx auth failures not done yet. +# +# Author: Daniel Black diff --git a/config/filter.d/nsd.conf b/config/filter.d/nsd.conf new file mode 100644 index 00000000..cd4ce35f --- /dev/null +++ b/config/filter.d/nsd.conf @@ -0,0 +1,26 @@ +# Fail2Ban configuration file +# +# Author: Bas van den Dikkenberg +# +# + +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + +[Definition] + +_daemon = nsd + +# 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 +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# Values: TEXT + +failregex = ^\[\]%(__prefix_line)sinfo: ratelimit block .* query TYPE255$ + ^\[\]%(__prefix_line)sinfo: .* refused, no acl matches\.$ diff --git a/config/filter.d/proftpd.conf b/config/filter.d/proftpd.conf index bf8f9b5f..ac714cc1 100644 --- a/config/filter.d/proftpd.conf +++ b/config/filter.d/proftpd.conf @@ -1,5 +1,7 @@ # Fail2Ban fitler for the Proftpd FTP daemon # +# Set "UseReverseDNS off" in proftpd.conf to avoid the need for DNS. +# See: http://www.proftpd.org/docs/howto/DNS.html [INCLUDES] diff --git a/config/filter.d/recidive.conf b/config/filter.d/recidive.conf index b29acaf3..13d2f53a 100644 --- a/config/filter.d/recidive.conf +++ b/config/filter.d/recidive.conf @@ -27,6 +27,6 @@ _daemon = fail2ban\.actions # jail using this filter 'recidive', or change this line! _jailname = recidive -failregex = ^(%(__prefix_line)s|,\d{3} fail2ban.actions:\s+)WARNING\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+\s*$ +failregex = ^(%(__prefix_line)s|,\d{3} fail2ban.actions%(__pid_re)s?:\s+)WARNING\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+\s*$ # Author: Tom Hendrikx, modifications by Amir Caspi diff --git a/config/filter.d/roundcube-auth.conf b/config/filter.d/roundcube-auth.conf index 279c5edd..b093f69c 100644 --- a/config/filter.d/roundcube-auth.conf +++ b/config/filter.d/roundcube-auth.conf @@ -9,8 +9,21 @@ before = common.conf [Definition] -failregex = ^\s*(\[(\s[+-][0-9]{4})?\])?(%(__hostname)s roundcube: IMAP Error)?: (FAILED login|Login failed) for .*? from (\. AUTHENTICATE .*)?\s*$ +failregex = ^\s*(\[(\s[+-][0-9]{4})?\])?(%(__hostname)s roundcube: IMAP Error)?: (FAILED login|Login failed) for .*? from (\. .* in .*?/rcube_imap\.php on line \d+ \(\S+ \S+\))?$ ignoreregex = - -# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge +# DEV Notes: +# +# Source: https://github.com/roundcube/roundcubemail/blob/master/program/lib/Roundcube/rcube_imap.php#L180 +# +# Part after comes straight from IMAP server up until the " in ....." +# Earlier versions didn't log the IMAP response hence optional. +# +# DoS resistance: +# +# Assume that the user can inject "from " into the imap response +# somehow. Write test cases around this to ensure that the combination of +# arbitary user input and IMAP response doesn't inject the wrong IP for +# fail2ban +# +# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge & Daniel Black diff --git a/config/filter.d/solid-pop3d.conf b/config/filter.d/solid-pop3d.conf new file mode 100644 index 00000000..d97cc134 --- /dev/null +++ b/config/filter.d/solid-pop3d.conf @@ -0,0 +1,32 @@ +# Fail2Ban filter for unsuccesful solid-pop3 authentication attempts +# +# Doesn't currently provide PAM support as PAM log messages don't include rhost as +# remote IP. +# +[INCLUDES] + +before = common.conf + +[Definition] + +_daemon = solid-pop3d + +failregex = ^%(__prefix_line)sauthentication failed: (no such user|can't map user name): .*? - $ + ^%(__prefix_line)s(APOP )?authentication failed for (mapped )?user .*? - $ + ^%(__prefix_line)sroot login not allowed - $ + ^%(__prefix_line)scan't find APOP secret for user .*? - $ + +ignoreregex = + +# DEV Notes: +# +# solid-pop3d needs to be compiled with --enable-logextend to support +# IP addresses in log messages. +# +# solid-pop3d-0.15/src/main.c contains all authentication errors +# except for PAM authentication messages ( src/authenticate.c ) +# +# A pam authentication failure message (note no IP for rhost). +# Nov 17 23:17:50 emf1pt2-2-35-70 solid-pop3d[17176]: pam_unix(solid-pop3d:auth): authentication failure; logname= uid=0 euid=0 tty= ruser= rhost= user=jacques +# +# Authors: Daniel Black diff --git a/config/filter.d/squid.conf b/config/filter.d/squid.conf new file mode 100644 index 00000000..da282692 --- /dev/null +++ b/config/filter.d/squid.conf @@ -0,0 +1,13 @@ +# Fail2Ban filter for Squid attempted proxy bypasses +# +# + +[Definition] + +failregex = ^\s+\d\s\s+[A-Z_]+_DENIED/403 .*$ + ^\s+\d\s\s+NONE/405 .*$ + + + +# Author: Daniel Black + diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index 08456177..a36b050c 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -14,7 +14,7 @@ _daemon = sshd failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from ( via \S+)?\s*$ ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from \s*$ - ^%(__prefix_line)sFailed \S+ for .* from (?: port \d*)?(?: ssh\d*)?(: (ruser .{0,100}|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".{0,100}", client host ".{0,100}")?))?\s*$ + ^%(__prefix_line)sFailed \S+ for .*? from (?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$ ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM \s*$ ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from \s*$ ^%(__prefix_line)sUser .+ from not allowed because not listed in AllowUsers\s*$ @@ -26,4 +26,11 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro ignoreregex = +# DEV Notes: +# +# "Failed \S+ for .*? from ..." failregex uses non-greedy catch-all because +# it is coming before use of which is not hard-anchored at the end as well, +# and later catch-all's could contain user-provided input, which need to be greedily +# matched away first. +# # Author: Cyril Jaquier, Yaroslav Halchenko, Petr Voralek, Daniel Black diff --git a/config/filter.d/wuftpd.conf b/config/filter.d/wuftpd.conf index 942de82a..45149f60 100644 --- a/config/filter.d/wuftpd.conf +++ b/config/filter.d/wuftpd.conf @@ -11,8 +11,11 @@ before = common.conf [Definition] _daemon = wu-ftpd +__pam_re=\(?pam_unix(?:\(wu-ftpd:auth\))?\)?:? failregex = ^%(__prefix_line)sfailed login from \S+ \[\]\s*$ + ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=(ftp)? ruser=\S* rhost=(?:\s+user=.*)?\s*$ + ignoreregex = diff --git a/config/jail.conf b/config/jail.conf index 23e30c83..3b8220e5 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -181,6 +181,14 @@ logpath = /var/log/apache*/*error.log maxretry = 6 +[nginx-http-auth] + +enabled = false +filter = nginx-http-auth +action = iptables-multiport[name=nginx-http-auth,port="80,443"] +logpath = /var/log/nginx/error.log + + # The hosts.deny path can be defined with the "file" argument if it is # not in /etc. [postfix-tcpwrapper] @@ -381,7 +389,7 @@ logpath = /var/log/mysqld.log maxretry = 5 -[mysqld-syslog-iptables] +[mysqld-syslog] enabled = false filter = mysqld-auth @@ -400,7 +408,7 @@ maxretry = 5 enabled = false filter = recidive logpath = /var/log/fail2ban.log -action = iptables-allports[name=recidive] +action = iptables-allports[name=recidive,protocol=all] sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log] bantime = 604800 ; 1 week findtime = 86400 ; 1 day @@ -498,7 +506,7 @@ logpath = /var/log/auth.log enabled = false filter = dovecot -action = iptables-multiport[name=dovecot, port="pop3,pop3s,imap,imaps,submission,smtps,sieve", protocol=tcp] +action = iptables-multiport[name=dovecot, port="pop3,pop3s,imap,imaps,submission,465,sieve", protocol=tcp] logpath = /var/log/mail.log @@ -506,13 +514,36 @@ logpath = /var/log/mail.log enabled = false filter = dovecot -action = iptables-multiport[name=dovecot-auth, port="pop3,pop3s,imap,imaps,submission,smtps,sieve", protocol=tcp] +action = iptables-multiport[name=dovecot-auth, port="pop3,pop3s,imap,imaps,submission,465,sieve", protocol=tcp] logpath = /var/log/secure +[solid-pop3d] + +enabled = false +filter = solid-pop3d +action = iptables-multiport[name=solid-pop3, port="pop3,pop3s", protocol=tcp] +logpath = /var/log/mail.log + + [selinux-ssh] enabled = false filter = selinux-ssh action = iptables[name=SELINUX-SSH, port=ssh, protocol=tcp] logpath = /var/log/audit/audit.log maxretry = 5 + +# See the IMPORTANT note in action.d/blocklist_de.conf for when to +# use this action +# +# Report block via blocklist.de fail2ban reporting service API +# See action.d/blocklist_de.conf for more information +[ssh-blocklist] + +enabled = false +filter = sshd +action = iptables[name=SSH, port=ssh, protocol=tcp] + sendmail-whois[name=SSH, dest=you@example.com, sender=fail2ban@example.com, sendername="Fail2Ban"] + blocklist_de[email="fail2ban@example.com", apikey="xxxxxx", service=%(filter)s] +logpath = /var/log/sshd.log +maxretry = 20 diff --git a/fail2ban-testcases b/fail2ban-testcases index f44e84d4..21b8fda4 100755 --- a/fail2ban-testcases +++ b/fail2ban-testcases @@ -173,7 +173,9 @@ tests.addTest(unittest.makeSuite(misctestcase.CustomDateFormatsTest)) # Filter if not opts.no_network: - tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP)) + tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIPDNS)) +tests.addTest(unittest.makeSuite(filtertestcase.IgnoreIP)) +tests.addTest(unittest.makeSuite(filtertestcase.BasicFilter)) tests.addTest(unittest.makeSuite(filtertestcase.LogFile)) tests.addTest(unittest.makeSuite(filtertestcase.LogFileMonitor)) if not opts.no_network: diff --git a/files/fail2ban-logrotate b/files/fail2ban-logrotate index 67c6364a..a09870af 100644 --- a/files/fail2ban-logrotate +++ b/files/fail2ban-logrotate @@ -13,6 +13,6 @@ missingok compress postrotate - /usr/bin/fail2ban-client set logtarget /var/log/fail2ban.log 1>/dev/null || true + /usr/bin/fail2ban-client flushlogs 1>/dev/null || true endscript } diff --git a/files/gen_badbots b/files/gen_badbots new file mode 100755 index 00000000..278058f7 --- /dev/null +++ b/files/gen_badbots @@ -0,0 +1,75 @@ +#!/bin/bash +#-------------------------- =+- Shell script -+= -------------------------- +# +# Yaroslav Halchenko CS@UNM, CS@NJIT +# web: http://www.onerussian.com & PSYCH@RUTGERS +# e-mail: yoh@onerussian.com ICQ#: 60653192 +# +# DESCRIPTION (NOTES): +# +# Script to fetch list of agent strings from http://www.user-agents.org +# which are known to be from mailicious bots, and create apache-badbots.conf +# filter for fail2ban +# +# COPYRIGHT: Yaroslav Halchenko 2007-2013 +# +# LICENSE: +# +# This program 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. +# +# This program 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 this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# On Debian system see /usr/share/common-licenses/GPL for the full license. +# +#-----------------\____________________________________/------------------ + +url=http://www.user-agents.org/index.shtml +badbots=$( +for f in "" "?g_m" "?moz" "?n_s" "?t_z"; do + wget -q -O- $url$f; +done \ +| grep -h -B4 'S '\ +| sed -e 's/ //g' \ +| awk '/^--/{getline; gsub(" ",""); print $0}' \ +| sed -e 's/\([.\:|()]\)/\\\1/g' \ +| uniq \ +| tr '\n' '|' \ +| sed -e 's/|$//g' +) + +echo $badbots >| /tmp/badbots.tmp + +cat >| config/filter.d/apache-badbots.conf < -.*"(GET|POST).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$ + +ignoreregex = + +# DEV Notes: +# List of bad bots fetched from http://www.user-agents.org +# Generated on `date` by $0. +# +# Author: Yaroslav Halchenko +EOF diff --git a/files/nagios/check_fail2ban b/files/nagios/check_fail2ban index 77a63393..9bf14305 100755 --- a/files/nagios/check_fail2ban +++ b/files/nagios/check_fail2ban @@ -165,7 +165,7 @@ if (($critical < 0) or ($warning < 0) or ($critical < $warning)) { # Core script # ----------- -my ($how_many_jail,$how_many_banned,$return_print,$plugstate) = (0,0,"","OK"); +my ($how_many_jail,$how_many_banned,$return_print,$perf_print,$plugstate) = (0,0,"","","OK"); ### Test the connection to the fail2ban server @@ -190,6 +190,7 @@ if ($jail_specific) { else { $how_many_banned = int($current_ban_number); $return_print = $how_many_banned.' current banned IP(s) for the specific jail '.$jail_specific; + $perf_print .= "$jail_name.currentBannedIP=$current_ban_number " if ($perfdata_value); } } ### To analyze all the jail @@ -214,6 +215,7 @@ else { else { print "DEBUG : the jail $jail_name has currently $current_ban_number banned IPs\n" if ($verbose_value); $how_many_banned += int($current_ban_number); + $perf_print .= "$jail_name.currentBannedIP=$current_ban_number " if ($perfdata_value); } } $return_print = $how_many_jail.' detected jails with '.$how_many_banned.' current banned IP(s)'; @@ -224,7 +226,7 @@ $plugstate = "CRITICAL" if ($how_many_banned >= $critical); $plugstate = "WARNING" if (($how_many_banned >= $warning) && ($how_many_banned < $critical)); $return_print = $display." - ".$plugstate." - ".$return_print; -$return_print .= " | currentBannedIP=$how_many_banned" if ($perfdata_value); +$return_print .= " | $perf_print" if ($perfdata_value); print $return_print; exit $ERRORS{"$plugstate"}; diff --git a/files/redhat-initd b/files/redhat-initd index 43147c95..08eb0f3c 100755 --- a/files/redhat-initd +++ b/files/redhat-initd @@ -1,6 +1,6 @@ #!/bin/bash # -# chkconfig: 345 92 08 +# chkconfig: - 92 08 # processname: fail2ban-server # config: /etc/fail2ban/fail2ban.conf # pidfile: /var/run/fail2ban/fail2ban.pid diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index aab2dde4..c5ccb803 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12. -.TH FAIL2BAN-CLIENT "1" "October 2013" "fail2ban-client v0.8.11.pre1" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. +.TH FAIL2BAN-CLIENT "1" "November 2013" "fail2ban-client v0.8.11" "User Commands" .SH NAME fail2ban-client \- configure and control the server .SH SYNOPSIS .B fail2ban-client [\fIOPTIONS\fR] \fI\fR .SH DESCRIPTION -Fail2Ban v0.8.11.pre1 reads log file that contains password failure report +Fail2Ban v0.8.11 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1 index 4a0e272b..e2c99565 100644 --- a/man/fail2ban-regex.1 +++ b/man/fail2ban-regex.1 @@ -1,5 +1,5 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12. -.TH FAIL2BAN-REGEX "1" "October 2013" "fail2ban-regex 0.8.11.pre1" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. +.TH FAIL2BAN-REGEX "1" "November 2013" "fail2ban-regex 0.8.11" "User Commands" .SH NAME fail2ban-regex \- test Fail2ban "failregex" option .SH SYNOPSIS diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index 7e09b49e..147bdeaa 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12. -.TH FAIL2BAN-SERVER "1" "October 2013" "fail2ban-server v0.8.11.pre1" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. +.TH FAIL2BAN-SERVER "1" "November 2013" "fail2ban-server v0.8.11" "User Commands" .SH NAME fail2ban-server \- start the server .SH SYNOPSIS .B fail2ban-server [\fIOPTIONS\fR] .SH DESCRIPTION -Fail2Ban v0.8.11.pre1 reads log file that contains password failure report +Fail2Ban v0.8.11 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .PP Only use this command for debugging purpose. Start the server with diff --git a/man/jail.conf.5 b/man/jail.conf.5 index 8ea44316..6c114c56 100644 --- a/man/jail.conf.5 +++ b/man/jail.conf.5 @@ -63,6 +63,12 @@ Comments: use '#' for comment lines and ';' (following a space) for inline comme .SH DEFAULT The following options are applicable to all jails. Their meaning is described in the default \fIjail.conf\fR file. .TP +\fBfilter\fR +.TP +\fBlogpath\fR +.TP +\fBaction\fR +.TP \fBignoreip\fR .TP \fBbantime\fR @@ -74,6 +80,10 @@ The following options are applicable to all jails. Their meaning is described in \fBbackend\fR .TP \fBusedns\fR +.TP +\fBfailregex\fR +.TP +\fBignoreregex\fR .SH "ACTION FILES" diff --git a/server/datedetector.py b/server/datedetector.py index ab2dd174..b12a8d46 100644 --- a/server/datedetector.py +++ b/server/datedetector.py @@ -101,6 +101,13 @@ class DateDetector: template.setRegex("\d{2}/\d{2}/\d{4}:\d{2}:\d{2}:\d{2}") template.setPattern("%m/%d/%Y:%H:%M:%S") self._appendTemplate(template) + # proftpd 2013-11-16 21:43:03,296 + # So like Exim below but with ,subsecond + template = DateStrptime() + template.setName("Year-Month-Day Hour:Minute:Second[,subsecond]") + template.setRegex("\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d+") + template.setPattern("%Y-%m-%d %H:%M:%S,%f") + self._appendTemplate(template) # Exim 2006-12-21 06:43:20 template = DateStrptime() template.setName("Year-Month-Day Hour:Minute:Second") diff --git a/server/datetemplate.py b/server/datetemplate.py index decaee1c..33c69703 100644 --- a/server/datetemplate.py +++ b/server/datetemplate.py @@ -78,7 +78,7 @@ class DateEpoch(DateTemplate): def __init__(self): DateTemplate.__init__(self) - self.setRegex("(?:^|(?P(?<=audit\()))\d{10}(?:\.\d{3,6})?(?(selinux)(?=:\d+\)))") + self.setRegex("(?:^|(?P(?<=^\[))|(?P(?<=audit\()))\d{10}(?:\.\d{3,6})?(?(selinux)(?=:\d+\))(?(square)(?=\])))") def getDate(self, line): date = None diff --git a/server/filter.py b/server/filter.py index 80433c01..b92289ef 100644 --- a/server/filter.py +++ b/server/filter.py @@ -219,6 +219,9 @@ class Filter(JailThread): # to enable banip fail2ban-client BAN command def addBannedIP(self, ip): + if self.inIgnoreIPList(ip): + logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.' % ip) + unixTime = MyTime.time() for i in xrange(self.failManager.getMaxRetry()): self.failManager.addFailure(FailTicket(ip, unixTime)) @@ -443,7 +446,7 @@ class FileFilter(Filter): self._delLogPath(path) return - def _delLogPath(self, path): + def _delLogPath(self, path): # pragma: no cover - overwritten function # nothing to do by default # to be overridden by backends pass @@ -565,6 +568,9 @@ class FileContainer: def getFileName(self): return self.__filename + def getPos(self): + return self.__pos + def open(self): self.__handler = open(self.__filename) # Set the file descriptor to be FD_CLOEXEC diff --git a/server/jail.py b/server/jail.py index 5e60ec7f..7ce12e46 100644 --- a/server/jail.py +++ b/server/jail.py @@ -102,9 +102,11 @@ class Jail: self.__filter = FilterPyinotify(self) def setName(self, name): + # 20 based on iptable chain name limit of 30 less len('fail2ban-') if len(name) >= 20: - logSys.warning("Jail name %r might be too long and some commands " - "might not function correctly. Please shorten" + logSys.warning("Jail name %r might be too long and some commands" + " (e.g. iptables) might not function correctly." + " Please shorten" % name) self.__name = name diff --git a/server/server.py b/server/server.py index 773fdf0b..736e9e10 100644 --- a/server/server.py +++ b/server/server.py @@ -347,13 +347,12 @@ class Server: try: self.__loggingLock.acquire() # set a format which is simpler for console use - formatter = logging.Formatter("%(asctime)s %(name)-16s: %(levelname)-6s %(message)s") + formatter = logging.Formatter("%(asctime)s %(name)-16s[%(process)d]: %(levelname)-7s %(message)s") if target == "SYSLOG": # Syslog daemons already add date to the message. - formatter = logging.Formatter("%(name)-16s: %(levelname)-6s %(message)s") + formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s") facility = logging.handlers.SysLogHandler.LOG_DAEMON - hdlr = logging.handlers.SysLogHandler("/dev/log", - facility = facility) + hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility) elif target == "STDOUT": hdlr = logging.StreamHandler(sys.stdout) elif target == "STDERR": @@ -362,7 +361,7 @@ class Server: # Target should be a file try: open(target, "a").close() - hdlr = logging.FileHandler(target) + hdlr = logging.handlers.RotatingFileHandler(target) except IOError: logSys.error("Unable to log to " + target) logSys.info("Logging to previous target " + self.__logTarget) @@ -402,6 +401,17 @@ class Server: finally: self.__loggingLock.release() + def flushLogs(self): + if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']: + for handler in logging.getLogger("fail2ban").handlers: + handler.doRollover() + return "rolled over" + else: + for handler in logging.getLogger("fail2ban").handlers: + handler.flush() + return "flushed" + + def __createDaemon(self): # pragma: no cover """ Detach a process from the controlling terminal and run it in the background as a daemon. @@ -409,6 +419,14 @@ class Server: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 """ + # When the first child terminates, all processes in the second child + # are sent a SIGHUP, so it's ignored. + + # We need to set this in the parent process, so it gets inherited by the + # child process, and this makes sure that it is effect even if the parent + # terminates quickly. + signal.signal(signal.SIGHUP, signal.SIG_IGN) + try: # Fork a child process so the parent can exit. This will return control # to the command line or shell. This is required so that the new process @@ -431,10 +449,6 @@ class Server: # leader. os.setsid() - # When the first child terminates, all processes in the second child - # are sent a SIGHUP, so it's ignored. - signal.signal(signal.SIGHUP, signal.SIG_IGN) - try: # Fork a second child to prevent zombies. Since the first child is # a session leader without a controlling terminal, it's possible for diff --git a/server/transmitter.py b/server/transmitter.py index deaf9adf..cf65dada 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -92,6 +92,8 @@ class Transmitter: value = command[1] time.sleep(int(value)) return None + elif command[0] == "flushlogs": + return self.__server.flushLogs() elif command[0] == "set": return self.__commandSet(command[1:]) elif command[0] == "get": diff --git a/testcases/actiontestcase.py b/testcases/actiontestcase.py index 14356ee5..e0587b3d 100644 --- a/testcases/actiontestcase.py +++ b/testcases/actiontestcase.py @@ -24,40 +24,23 @@ __author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest, time +import time import logging, sys from server.action import Action -from StringIO import StringIO +from utils import LogCaptureTestCase -class ExecuteAction(unittest.TestCase): +class ExecuteAction(LogCaptureTestCase): def setUp(self): """Call before every test case.""" self.__action = Action("Test") - - # For extended testing of what gets output into logging - # system, we will redirect it to a string - logSys = logging.getLogger("fail2ban") - - # Keep old settings - self._old_level = logSys.level - self._old_handlers = logSys.handlers - # Let's log everything into a string - self._log = StringIO() - logSys.handlers = [logging.StreamHandler(self._log)] - logSys.setLevel(getattr(logging, 'DEBUG')) + LogCaptureTestCase.setUp(self) def tearDown(self): """Call after every test case.""" - # print "O: >>%s<<" % self._log.getvalue() - logSys = logging.getLogger("fail2ban") - logSys.handlers = self._old_handlers - logSys.level = self._old_level + LogCaptureTestCase.tearDown(self) self.__action.execActionStop() - def _is_logged(self, s): - return s in self._log.getvalue() - def testNameChange(self): self.assertEqual(self.__action.getName(), "Test") self.__action.setName("Tricky Test") diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 773d5072..28387703 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -27,6 +27,7 @@ from client.configreader import ConfigReader from client.jailreader import JailReader from client.jailsreader import JailsReader from client.configurator import Configurator +from utils import LogCaptureTestCase class ConfigReaderTest(unittest.TestCase): @@ -106,7 +107,31 @@ option = %s self.assertEqual(self._getoption(), 1) -class JailReaderTest(unittest.TestCase): +class JailReaderTest(LogCaptureTestCase): + + def testJailActionEmpty(self): + jail = JailReader('emptyaction', basedir=os.path.join('testcases','config')) + self.assertTrue(jail.read()) + self.assertTrue(jail.getOptions()) + self.assertTrue(jail.isEnabled()) + self.assertTrue(self._is_logged('No filter set for jail emptyaction')) + self.assertTrue(self._is_logged('No actions were defined for emptyaction')) + + def testJailActionFilterMissing(self): + jail = JailReader('missingbitsjail', basedir=os.path.join('testcases','config')) + self.assertTrue(jail.read()) + self.assertFalse(jail.getOptions()) + self.assertTrue(jail.isEnabled()) + self.assertTrue(self._is_logged("Found no accessible config files for 'filter.d/catchallthebadies' under testcases/config")) + self.assertTrue(self._is_logged('Unable to read the filter')) + + def testJailActionBrokenDef(self): + jail = JailReader('brokenactiondef', basedir=os.path.join('testcases','config')) + self.assertTrue(jail.read()) + self.assertFalse(jail.getOptions()) + self.assertTrue(jail.isEnabled()) + self.assertTrue(self._is_logged('Error in action definition joho[foo')) + self.assertTrue(self._is_logged('Caught exception: While reading action joho[foo we should have got 1 or 2 groups. Got: 0')) def testStockSSHJail(self): jail = JailReader('ssh-iptables', basedir='config') # we are running tests from root project dir atm @@ -114,33 +139,111 @@ class JailReaderTest(unittest.TestCase): self.assertTrue(jail.getOptions()) self.assertFalse(jail.isEnabled()) self.assertEqual(jail.getName(), 'ssh-iptables') + jail.setName('ssh-funky-blocker') + self.assertEqual(jail.getName(), 'ssh-funky-blocker') def testSplitAction(self): action = "mail-whois[name=SSH]" expected = ['mail-whois', {'name': 'SSH'}] result = JailReader.splitAction(action) self.assertEqual(expected, result) + + self.assertEqual(['mail.who_is', {}], JailReader.splitAction("mail.who_is")) + self.assertEqual(['mail.who_is', {'a':'cat', 'b':'dog'}], JailReader.splitAction("mail.who_is[a=cat,b=dog]")) + self.assertEqual(['mail--ho_is', {}], JailReader.splitAction("mail--ho_is")) + + self.assertEqual(['mail--ho_is', {}], JailReader.splitAction("mail--ho_is['s']")) + self.assertTrue(self._is_logged("Invalid argument ['s'] in ''s''")) + + self.assertEqual(['mail', {'a': ','}], JailReader.splitAction("mail[a=',']")) + self.assertRaises(ValueError, JailReader.splitAction ,'mail-how[') + + def testGlob(self): d = tempfile.mkdtemp(prefix="f2b-temp") # Generate few files # regular file - open(os.path.join(d, 'f1'), 'w').close() + f1 = os.path.join(d, 'f1') + open(f1, 'w').close() # dangling link - os.symlink('nonexisting', os.path.join(d, 'f2')) + + f2 = os.path.join(d, 'f2') + os.symlink('nonexisting',f2) # must be only f1 - self.assertEqual(JailReader._glob(os.path.join(d, '*')), [os.path.join(d, 'f1')]) + self.assertEqual(JailReader._glob(os.path.join(d, '*')), [f1]) # since f2 is dangling -- empty list - self.assertEqual(JailReader._glob(os.path.join(d, 'f2')), []) + self.assertEqual(JailReader._glob(f2), []) + self.assertTrue(self._is_logged('File %s is a dangling link, thus cannot be monitored' % f2)) + self.assertEqual(JailReader._glob(os.path.join(d, 'nonexisting')), []) + os.remove(f1) + os.remove(f2) + os.rmdir(d) -class JailsReaderTest(unittest.TestCase): +class JailsReaderTest(LogCaptureTestCase): def testProvidingBadBasedir(self): if not os.path.exists('/XXX'): reader = JailsReader(basedir='/XXX') self.assertRaises(ValueError, reader.read) + def testReadTestJailConf(self): + jails = JailsReader(basedir=os.path.join('testcases','config')) + self.assertTrue(jails.read()) + self.assertFalse(jails.getOptions()) + self.assertRaises(ValueError, jails.convert) + comm_commands = jails.convert(allow_no_files=True) + self.maxDiff = None + self.assertEqual(sorted(comm_commands), + sorted([['add', 'emptyaction', 'auto'], + ['set', 'emptyaction', 'usedns', 'warn'], + ['set', 'emptyaction', 'maxretry', 3], + ['set', 'emptyaction', 'findtime', 600], + ['set', 'emptyaction', 'bantime', 600], + ['add', 'special', 'auto'], + ['set', 'special', 'usedns', 'warn'], + ['set', 'special', 'maxretry', 3], + ['set', 'special', 'addfailregex', ''], + ['set', 'special', 'findtime', 600], + ['set', 'special', 'bantime', 600], + ['add', 'missinglogfiles', 'auto'], + ['set', 'missinglogfiles', 'usedns', 'warn'], + ['set', 'missinglogfiles', 'maxretry', 3], + ['set', 'missinglogfiles', 'findtime', 600], + ['set', 'missinglogfiles', 'bantime', 600], + ['set', 'missinglogfiles', 'addfailregex', ''], + ['add', 'brokenaction', 'auto'], + ['set', 'brokenaction', 'usedns', 'warn'], + ['set', 'brokenaction', 'maxretry', 3], + ['set', 'brokenaction', 'findtime', 600], + ['set', 'brokenaction', 'bantime', 600], + ['set', 'brokenaction', 'addfailregex', ''], + ['set', 'brokenaction', 'addaction', 'brokenaction'], + ['set', + 'brokenaction', + 'actionban', + 'brokenaction', + 'hit with big stick '], + ['set', 'brokenaction', 'actionstop', 'brokenaction', ''], + ['set', 'brokenaction', 'actionstart', 'brokenaction', ''], + ['set', 'brokenaction', 'actionunban', 'brokenaction', ''], + ['set', 'brokenaction', 'actioncheck', 'brokenaction', ''], + ['add', 'parse_to_end_of_jail.conf', 'auto'], + ['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'], + ['set', 'parse_to_end_of_jail.conf', 'maxretry', 3], + ['set', 'parse_to_end_of_jail.conf', 'findtime', 600], + ['set', 'parse_to_end_of_jail.conf', 'bantime', 600], + ['set', 'parse_to_end_of_jail.conf', 'addfailregex', ''], + ['start', 'emptyaction'], + ['start', 'special'], + ['start', 'missinglogfiles'], + ['start', 'brokenaction'], + ['start', 'parse_to_end_of_jail.conf'],])) + self.assertTrue(self._is_logged("Errors in jail 'missingbitsjail'. Skipping...")) + self.assertTrue(self._is_logged("No file(s) found for glob /weapons/of/mass/destruction")) + + def testReadStockJailConf(self): jails = JailsReader(basedir='config') # we are running tests from root project dir atm self.assertTrue(jails.read()) # opens fine @@ -153,6 +256,7 @@ class JailsReaderTest(unittest.TestCase): # We should not "read" some bogus jail old_comm_commands = comm_commands[:] # make a copy self.assertFalse(jails.getOptions("BOGUS")) + self.assertTrue(self._is_logged("No section: 'BOGUS'")) # and there should be no side-effects self.assertEqual(jails.convert(), old_comm_commands) diff --git a/testcases/config/action.d/brokenaction.conf b/testcases/config/action.d/brokenaction.conf new file mode 100644 index 00000000..59e97b7f --- /dev/null +++ b/testcases/config/action.d/brokenaction.conf @@ -0,0 +1,4 @@ + +[Definition] + +actionban = hit with big stick diff --git a/testcases/config/fail2ban.conf b/testcases/config/fail2ban.conf new file mode 100644 index 00000000..36984c78 --- /dev/null +++ b/testcases/config/fail2ban.conf @@ -0,0 +1,5 @@ +[Definition] + +# 3 = INFO +loglevel = 3 + diff --git a/testcases/config/filter.d/simple.conf b/testcases/config/filter.d/simple.conf new file mode 100644 index 00000000..4a2c0bb7 --- /dev/null +++ b/testcases/config/filter.d/simple.conf @@ -0,0 +1,4 @@ + +[Definition] + +failregex = diff --git a/testcases/config/jail.conf b/testcases/config/jail.conf new file mode 100644 index 00000000..525308e3 --- /dev/null +++ b/testcases/config/jail.conf @@ -0,0 +1,33 @@ + +[DEFAULT] +filter = simple +logpath = /non/exist + +[emptyaction] +enabled = true +filter = +action = + +[special] +failregex = +ignoreregex = +ignoreip = + +[missinglogfiles] +logpath = /weapons/of/mass/destruction + +[brokenactiondef] +enabled = true +action = joho[foo + +[brokenaction] +enabled = true +action = brokenaction + +[missingbitsjail] +filter = catchallthebadies +action = thefunkychickendance + +[parse_to_end_of_jail.conf] +enabled = true +action = diff --git a/testcases/datedetectortestcase.py b/testcases/datedetectortestcase.py index de324aa6..4cc7dcfc 100644 --- a/testcases/datedetectortestcase.py +++ b/testcases/datedetectortestcase.py @@ -74,6 +74,7 @@ class DateDetectorTest(unittest.TestCase): (False, "23/Jan/2005:21:59:59"), (False, "01/23/2005:21:59:59"), (False, "2005-01-23 21:59:59"), + (False, "2005-01-23 21:59:59,099"), # proftpd (False, "23-Jan-2005 21:59:59"), (False, "23-01-2005 21:59:59"), (False, "01-23-2005 21:59:59.252"), # reported on f2b, causes Feb29 fix to break diff --git a/testcases/files/logs/apache-auth b/testcases/files/logs/apache-auth index d6c40ac5..787d160a 100644 --- a/testcases/files/logs/apache-auth +++ b/testcases/files/logs/apache-auth @@ -115,3 +115,6 @@ # failJSON: { "time": "2013-06-01T02:17:42", "match": true , "host": "192.168.0.2" } [Sat Jun 01 02:17:42 2013] [error] [client 192.168.0.2] user root not found + +# failJSON: { "time": "2013-11-18T22:39:33", "match": true , "host": "91.49.82.139" } +[Mon Nov 18 22:39:33 2013] [error] [client 91.49.82.139] user gg not found: /, referer: http://sj.hopto.org/management.html diff --git a/testcases/files/logs/apache-overflows b/testcases/files/logs/apache-overflows index d40c1c4f..376114c4 100644 --- a/testcases/files/logs/apache-overflows +++ b/testcases/files/logs/apache-overflows @@ -1,4 +1,25 @@ +# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=574182 # failJSON: { "time": "2010-03-16T15:39:29", "match": true , "host": "58.179.109.179" } [Tue Mar 16 15:39:29 2010] [error] [client 58.179.109.179] Invalid URI in request \xf9h\xa9\xf3\x88\x8cXKj \xbf-l*4\x87n\xe4\xfe\xd4\x1d\x06\x8c\xf8m\\rS\xf6n\xeb\x8 # failJSON: { "time": "2010-03-15T15:44:47", "match": true , "host": "121.222.2.133" } [Mon Mar 15 15:44:47 2010] [error] [client 121.222.2.133] Invalid URI in request n\xed*\xbe*\xab\xefd\x80\xb5\xae\xf6\x01\x10M?\xf2\xce\x13\x9c\xd7\xa0N\xa7\xdb%0\xde\xe0\xfc\xd2\xa0\xfe\xe9w\xee\xc4`v\x9b[{\x0c:\xcb\x93\xc6\xa0\x93\x9c`l\\\x8d\xc9 + +# http://forum.nconf.org/viewtopic.php?f=14&t=427&p=1488 +# failJSON: { "time": "2010-07-30T11:23:54", "match": true , "host": "10.85.6.69" } +[Fri Jul 30 11:23:54 2010] [error] [client 10.85.6.69] request failed: URI too long (longer than 8190) +# failJSON: { "time": "2010-10-27T23:16:37", "match": true , "host": "187.117.240.164" } +[Wed Oct 27 23:16:37 2010] [error] [client 187.117.240.164] Invalid URI in request x\xb2\xa1:SMl\xcc{\xfd"\xd1\x91\x84!d\x0e~\xf6:\xfbVu\xdf\xc3\xdb[\xa9\xfe\xd3lpz\x92\xbf\x9f5\xa3\xbbvF\xbc\xee\x1a\xb1\xb0\xf8K\xecE\xbc\xe8r\xacx=\xc7>\xb5\xbd\xa3\xda\xe9\xf09\x95"fd\x1c\x05\x1c\xd5\xf3#:\x91\xe6WE\xdb\xadN;k14;\xdcr\xad\x9e\xa8\xde\x95\xc3\xebw\xa0\xb1N\x8c~\xf1\xcfSY\xd5zX\xd7\x0f\vH\xe4\xb5(\xcf,3\xc98\x19\xefYq@\xd2I\x96\xfb\xc7\xa9\xae._{S\xd1\x9c\xad\x17\xdci\x9b\xca\x93\xafSM\xb8\x99\xd9|\xc2\xd8\xc9\xe7\xe9O\x99\xad\x19\xc3V]\xcc\xddR\xf7$\xaa\xb8\x18\xe0f\xb8\xff + + +# Could be apache-2.2 or earlier +# http://www.aota.net/forums/showthread.php?t=15796 +# failJSON: { "time": "2003-11-14T16:11:55", "match": true , "host": "1.2.3.4" } +[Fri Nov 14 16:11:55 2003] [error] [client 1.2.3.4] request failed: erroneous characters after protocol string: User-Agent: Mozilla/5.0 (Windows; U; Win98; en-US; m18) Gecko/20001108 Netscape6/6.0 + +# http://forum.directadmin.com/showthread.php?t=22412 +# failJSON: { "time": "2007-11-15T03:09:59", "match": true , "host": "89.189.71.87" } +[Thu Nov 15 03:09:59 2007] [error] [client 89.189.71.87] Invalid method in request NOOP + +# https://issues.apache.org/bugzilla/show_bug.cgi?id=46123 +# failJSON: { "time": "2008-10-29T11:55:14", "match": true , "host": "127.0.0.1" } +[Wed Oct 29 11:55:14 2008] [error] [client 127.0.0.1] Invalid method in request \x16\x03\x01 - possible attempt to establish SSL connection when the server isn't expecting it diff --git a/testcases/files/logs/asterisk b/testcases/files/logs/asterisk index b2eb7738..60c89d5f 100644 --- a/testcases/files/logs/asterisk +++ b/testcases/files/logs/asterisk @@ -40,3 +40,6 @@ [2009-12-22 16:35:24] NOTICE[14916]: chan_sip.c:15644 handle_request_subscribe: Sending fake auth rejection for user ;tag=6pwd6erg54 # failJSON: { "time": "2013-07-06T09:09:25", "match": true , "host": "141.255.164.106" } [2013-07-06 09:09:25] SECURITY[3308] res_security_log.c: SecurityEvent="InvalidPassword",EventTV="1373098165-824497",Severity="Error",Service="SIP",EventVersion="2",AccountID="972592891005",SessionID="0x88aab6c",LocalAddress="IPV4/UDP/92.28.73.180/5060",RemoteAddress="IPV4/UDP/141.255.164.106/5084",Challenge="41d26de5",ReceivedChallenge="41d26de5",ReceivedHash="7a6a3a2e95a05260aee612896e1b4a39" + +# failJSON: { "time": "2013-11-11T14:33:38", "match": true , "host": "192.168.55.152" } +[2013-11-11 14:33:38] WARNING[6756][C-0000001d] Ext. s: "Rejecting unknown SIP connection from 192.168.55.152" diff --git a/testcases/files/logs/dovecot b/testcases/files/logs/dovecot index d2aa59ca..aa79e65a 100644 --- a/testcases/files/logs/dovecot +++ b/testcases/files/logs/dovecot @@ -12,6 +12,9 @@ # failJSON: { "time": "2004-12-12T11:19:11", "match": true , "host": "190.210.136.21" } Dec 12 11:19:11 dunnart dovecot: pop3-login: Aborted login (tried to use disabled plaintext auth): rip=190.210.136.21, lip=113.212.99.193 +# failJSON: { "time": "2004-12-12T11:19:11", "match": true , "host": "190.210.136.21" } +Dec 12 11:19:11 dunnart dovecot: pop3-login: Aborted login (tried to use disallowed plaintext auth): rip=190.210.136.21, lip=113.212.99.193, session= + # failJSON: { "time": "2005-06-13T16:30:54", "match": true , "host": "49.176.98.87" } Jun 13 16:30:54 platypus dovecot: imap-login: Disconnected (auth failed, 2 attempts): user=, method=PLAIN, rip=49.176.98.87, lip=113.212.99.194, TLS # failJSON: { "time": "2005-06-14T00:48:21", "match": true , "host": "59.167.242.100" } diff --git a/testcases/files/logs/nginx-http-auth b/testcases/files/logs/nginx-http-auth new file mode 100644 index 00000000..0fa7a7bd --- /dev/null +++ b/testcases/files/logs/nginx-http-auth @@ -0,0 +1,6 @@ + +# failJSON: { "time": "2012-04-09T11:53:29", "match": true , "host": "192.0.43.10" } +2012/04/09 11:53:29 [error] 2865#0: *66647 user "xyz" was not found in "/var/www/.htpasswd", client: 192.0.43.10, server: www.myhost.com, request: "GET / HTTP/1.1", host: "www.myhost.com" +# failJSON: { "time": "2012-04-09T11:53:36", "match": true , "host": "192.0.43.10" } +2012/04/09 11:53:36 [error] 2865#0: *66647 user "xyz": password mismatch, client: 192.0.43.10, server: www.myhost.com, request: "GET / HTTP/1.1", host: "www.myhost.com" + diff --git a/testcases/files/logs/nsd b/testcases/files/logs/nsd new file mode 100644 index 00000000..a33a52a9 --- /dev/null +++ b/testcases/files/logs/nsd @@ -0,0 +1,4 @@ +# failJSON: { "time": "2013-12-17T14:58:14", "match": true , "host": "192.0.2.105" } +[1387288694] nsd[7745]: info: ratelimit block example.com. type any target 192.0.2.0/24 query 192.0.2.105 TYPE255 +# failJSON: { "time": "2013-12-18T07:42:15", "match": true , "host": "192.0.2.115" } +[1387348935] nsd[23600]: info: axfr for zone domain.nl. from client 192.0.2.115 refused, no acl matches. diff --git a/testcases/files/logs/proftpd b/testcases/files/logs/proftpd index 9687d992..b255e91e 100644 --- a/testcases/files/logs/proftpd +++ b/testcases/files/logs/proftpd @@ -14,3 +14,5 @@ Jun 14 00:09:59 platypus.ace-hosting.com.au proftpd[17839] platypus.ace-hosting. May 31 10:53:25 mail proftpd[15302]: xxxxxxxxxx (::ffff:1.2.3.4[::ffff:1.2.3.4]) - Maximum login attempts (3) exceeded # failJSON: { "time": "2004-12-05T15:44:32", "match": true , "host": "1.2.3.4" } Dec 5 15:44:32 serv1 proftpd[70944]: serv1.domain.com (example.com[1.2.3.4]) - USER jtittle@domain.org: no such user found from example.com [1.2.3.4] to 1.2.3.4:21 +# failJSON: { "time": "2013-11-16T21:59:30", "match": true , "host": "1.2.3.4", "desc": "proftpd-basic 1.3.5~rc3-2.1 on Debian uses date format with milliseconds if logging under /var/log/proftpd/proftpd.log" } +2013-11-16 21:59:30,121 novo proftpd[25891] localhost (andy[1.2.3.4]): USER kjsad: no such user found from andy [1.2.3.5] to ::ffff:192.168.1.14:21 diff --git a/testcases/files/logs/recidive b/testcases/files/logs/recidive index 6af85137..83acc3e1 100644 --- a/testcases/files/logs/recidive +++ b/testcases/files/logs/recidive @@ -1,5 +1,7 @@ # failJSON: { "time": "2006-02-13T15:52:30", "match": true , "host": "1.2.3.4" } 2006-02-13 15:52:30,388 fail2ban.actions: WARNING [sendmail] Ban 1.2.3.4 +# failJSON: { "time": "2006-02-13T15:52:30", "match": true , "host": "1.2.3.4", "desc": "Extended with [PID]" } +2006-02-13 15:52:30,388 fail2ban.actions[123]: WARNING [sendmail] Ban 1.2.3.4 # failJSON: { "match": false } 2006-02-13 16:07:31,183 fail2ban.actions: WARNING [sendmail] Unban 1.2.3.4 # failJSON: { "match": false } diff --git a/testcases/files/logs/roundcube-auth b/testcases/files/logs/roundcube-auth index 7c16efbd..43a42192 100644 --- a/testcases/files/logs/roundcube-auth +++ b/testcases/files/logs/roundcube-auth @@ -4,3 +4,21 @@ May 26 07:12:40 hamster roundcube: IMAP Error: Login failed for sales@example.com from 10.1.1.47 # failJSON: { "time": "2005-07-11T03:06:37", "match": true , "host": "1.2.3.4" } Jul 11 03:06:37 myhostname roundcube: IMAP Error: Login failed for admin from 1.2.3.4. AUTHENTICATE PLAIN: A0002 NO Login failed. in /usr/share/roundcube/program/include/rcube_imap.php on line 205 (POST /wmail/?_task=login&_action=login) + +# Made up to attempts to inject a DoS on the server. Assume the user can manipulate the IMAP error response +# +# user = admin from 127.0.0.1 +# failJSON: { "time": "2005-07-11T03:06:37", "match": true , "host": "1.2.3.4" } +Jul 11 03:06:37 myhostname roundcube: IMAP Error: Login failed for admin from 127.0.0.1 from 1.2.3.4. AUTHENTICATE PLAIN: A0002 NO Login failed. in /usr/share/roundcube/program/include/rcube_imap.php on line 205 (POST /wmail/?_task=login&_action=login) +# +# IMAP server logs user=${username} +# failJSON: { "time": "2005-07-11T03:06:37", "match": true , "host": "1.2.3.4" } +Jul 11 03:06:37 myhostname roundcube: IMAP Error: Login failed for admin from 127.0.0.1 from 1.2.3.4. AUTHENTICATE PLAIN: A0002 NO Login failed. user=admin from 127.0.0.1 in /usr/share/roundcube/program/include/rcube_imap.php on line 205 (POST /wmail/?_task=login&_action=login) +# +# Old roundcube version - no IMAP response +# failJSON: { "time": "2005-07-11T03:06:37", "match": true , "host": "1.2.3.4" } +Jul 11 03:06:37 myhostname roundcube: IMAP Error: Login failed for admin from 127.0.0.1 from 1.2.3.4 +# +# user = admin from 127.0.0.1 in +# failJSON: { "time": "2005-07-11T03:06:37", "match": true , "host": "1.2.3.4" } +Jul 11 03:06:37 myhostname roundcube: IMAP Error: Login failed for admin from 127.0.0.1 in from 1.2.3.4. AUTHENTICATE PLAIN: A0002 NO Login failed. user=admin from 127.0.0.1 in in /usr/share/roundcube/program/include/rcube_imap.php on line 205 (POST /wmail/?_task=login&_action=login) diff --git a/testcases/files/logs/solid-pop3d b/testcases/files/logs/solid-pop3d new file mode 100644 index 00000000..45c4ecaf --- /dev/null +++ b/testcases/files/logs/solid-pop3d @@ -0,0 +1,25 @@ +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: authentication failed: no such user: adrian - 123.33.44.45 + +# All below are manufactured from looking at log +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: authentication failed: can't map user name: adrian - 123.33.44.45 + +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: authentication failed for user adrain - 123.33.44.45 + +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: authentication failed for mapped user adrain - 123.33.44.45 + +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: root login not allowed - 123.33.44.45 + +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: can't find APOP secret for user adrian - 123.33.44.45 + +# failJSON: { "time": "2004-11-15T00:34:53", "match": true , "host": "123.33.44.45" } +Nov 15 00:34:53 rmc1pt2-2-35-70 solid-pop3d[3822]: APOP authentication failed for user adrian - 123.33.44.45 + +# Real log messages again: +# failJSON: { "time": "2004-11-17T23:10:03", "match": true , "host": "190.16.165.230" } +Nov 17 23:10:03 emf1pt2-2-35-70 solid-pop3d[16993]: authentication failed for user jacques - 190.16.165.230 diff --git a/testcases/files/logs/squid b/testcases/files/logs/squid new file mode 100644 index 00000000..fa2c593c --- /dev/null +++ b/testcases/files/logs/squid @@ -0,0 +1,13 @@ +# Logs thanks to Roman Gelfand +# +# failJSON: { "time": "2013-12-08T23:55:23", "match": true , "host": "91.188.124.227" } +1386543323.511 4 91.188.124.227 TCP_DENIED/403 4099 GET http://www.proxy-listen.de/azenv.php - HIER_NONE/- text/html + +# failJSON: { "time": "2013-12-08T23:58:20", "match": true , "host": "175.44.0.184" } +1386543500.220 5 175.44.0.184 NONE/405 3364 CONNECT error:method-not-allowed - HIER_NONE/- text/html + +# failJSON: { "time": "2013-12-09T00:08:04", "match": true , "host": "198.74.125.200" } +1386544084.763 3 198.74.125.200 TCP_DENIED/403 3722 GET http://www2t.biglobe.ne.jp/~take52/test/env.cgi - HIER_NONE/- text/html + +# failJSON: { "time": "2013-12-09T00:09:06", "match": true , "host": "175.42.91.151" } +1386544146.088 1 175.42.91.151 TCP_DENIED/403 3745 GET http://pkfsp.ru/wp-content/uploads/proxyc/engine.php - HIER_NONE/- text/html diff --git a/testcases/files/logs/sshd b/testcases/files/logs/sshd index 96338220..3c50dcfd 100644 --- a/testcases/files/logs/sshd +++ b/testcases/files/logs/sshd @@ -94,3 +94,12 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed hostbased for dan from 127.0.0.1 po # failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1" } Sep 29 17:15:02 spaceman sshd[12946]: Failed hostbased for dan from 127.0.0.1 port 45785 ssh2: DSA 01:c0:79:41:91:31:9a:7d:95:23:91:ac:b1:6d:59:81, client user "dan", client host "localhost.localdomain" + +# failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1", "desc": "Injecting into rhost for the format of OpenSSH >=6.3" } +Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser from 1.2.3.4 + +# 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 + +# 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 diff --git a/testcases/files/logs/suhosin b/testcases/files/logs/suhosin index 90ed7bf1..a9b0002a 100644 --- a/testcases/files/logs/suhosin +++ b/testcases/files/logs/suhosin @@ -2,3 +2,9 @@ Mar 11 22:52:12 lighttpd[53690]: (mod_fastcgi.c.2676) FastCGI-stderr: ALERT - configured request variable name length limit exceeded - dropped variable 'upqchi07vFfAFuBjnIKGIwiLrHo3Vt68T3yqvhQu2TqetQ78roy7Q6bpTfDUtYFR593/MA' (attacker '198.51.100.167', file '/usr/local/captiveportal/index.php') # failJSON: { "time": "2005-02-26T22:52:29", "match": true , "host": "198.51.100.77" } Feb 26 22:52:29 host suhosin[9636]: ALERT - script tried to increase memory_limit to 268435456 bytes which is above the allowed value (attacker '198.51.100.77', file '/var/www/wordpress/wp-admin/includes/image.php', line 161) + +# failJSON: { "time": "2004-11-18T20:18:31", "match": true , "host": "188.132.244.3" } +Nov 18 20:18:31 platypus suhosin[28433]: ALERT - ASCII-NUL chars not allowed within request variables - dropped variable 'templatefile' (attacker '188.132.244.3', file '/home/ace-hosting/public_html/cart.php') + +# failJSON: { "time": "2004-10-25T10:59:49", "match": true , "host": "38.111.147.83" } +Oct 25 10:59:49 platypus suhosin[13953]: ALERT - configured GET variable value length limit exceeded - dropped variable '_route_' (attacker '38.111.147.83', file '/home/thegoblin/public_html/index.php') diff --git a/testcases/files/logs/wuftpd b/testcases/files/logs/wuftpd index bbb816cc..948e848f 100644 --- a/testcases/files/logs/wuftpd +++ b/testcases/files/logs/wuftpd @@ -3,3 +3,5 @@ Oct 6 09:59:26 myserver wu-ftpd[18760]: failed login from hj-145-173-a8.bta.net.cn [202.108.145.173] # failJSON: { "time": "2004-10-11T16:45:07", "match": true , "host": "198.51.100.71" } Oct 11 16:45:07 ubuntu wu-ftpd[2360]: failed login from example.com [198.51.100.71] +# failJSON: { "time": "2005-03-22T09:35:02", "match": true , "host": "198.51.100.71" } +Mar 22 09:35:02 SiD wu-ftpd[31278]: pam_unix(wu-ftpd:auth): authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=198.51.100.71 user=root diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index 8bc24085..7b18c4bf 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -31,15 +31,16 @@ import tempfile from server.jail import Jail from server.filterpoll import FilterPoll -from server.filter import FileFilter, DNSUtils +from server.filter import Filter, FileFilter, DNSUtils from server.failmanager import FailManager from server.failmanager import FailManagerEmpty +from dummyjail import DummyJail # # Useful helpers # -from utils import mtimesleep +from utils import mtimesleep, LogCaptureTestCase # yoh: per Steven Hiscocks's insight while troubleshooting # https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836 @@ -144,14 +145,27 @@ def _copy_lines_between_files(fin, fout, n=None, skip=0, mode='a', terminal_line # Actual tests # -class IgnoreIP(unittest.TestCase): +class BasicFilter(unittest.TestCase): + + def setUp(self): + self.filter = Filter('name') + + def testGetSetUseDNS(self): + # default is warn + self.assertEqual(self.filter.getUseDns(), 'warn') + self.filter.setUseDns(True) + self.assertEqual(self.filter.getUseDns(), 'yes') + self.filter.setUseDns(False) + self.assertEqual(self.filter.getUseDns(), 'no') + + +class IgnoreIP(LogCaptureTestCase): def setUp(self): """Call before every test case.""" - self.filter = FileFilter(None) - - def tearDown(self): - """Call after every test case.""" + LogCaptureTestCase.setUp(self) + self.jail = DummyJail() + self.filter = FileFilter(self.jail) def testIgnoreIPOK(self): ipList = "127.0.0.1", "192.168.0.1", "255.255.255.255", "99.99.99.99" @@ -159,19 +173,47 @@ class IgnoreIP(unittest.TestCase): self.filter.addIgnoreIP(ip) self.assertTrue(self.filter.inIgnoreIPList(ip)) - # Test DNS - self.filter.addIgnoreIP("www.epfl.ch") - - self.assertTrue(self.filter.inIgnoreIPList("128.178.50.12")) def testIgnoreIPNOK(self): ipList = "", "999.999.999.999", "abcdef", "192.168.0." for ip in ipList: self.filter.addIgnoreIP(ip) self.assertFalse(self.filter.inIgnoreIPList(ip)) + + def testIgnoreIPCIDR(self): + self.filter.addIgnoreIP('192.168.1.0/25') + self.assertTrue(self.filter.inIgnoreIPList('192.168.1.0')) + self.assertTrue(self.filter.inIgnoreIPList('192.168.1.1')) + self.assertTrue(self.filter.inIgnoreIPList('192.168.1.127')) + self.assertFalse(self.filter.inIgnoreIPList('192.168.1.128')) + self.assertFalse(self.filter.inIgnoreIPList('192.168.1.255')) + self.assertFalse(self.filter.inIgnoreIPList('192.168.0.255')) + + def testIgnoreInProcessLine(self): + self.filter.addIgnoreIP('192.168.1.0/25') + self.filter.addFailRegex('') + self.filter.processLineAndAdd('Thu Jul 11 01:21:43 2013 192.168.1.32') + self.assertTrue(self._is_logged('Ignore 192.168.1.32')) + + def testIgnoreAddBannedIP(self): + self.filter.addIgnoreIP('192.168.1.0/25') + self.filter.addBannedIP('192.168.1.32') + self.assertFalse(self._is_logged('Ignore 192.168.1.32')) + self.assertTrue(self._is_logged('Requested to manually ban an ignored IP 192.168.1.32. User knows best. Proceeding to ban it.')) + + +class IgnoreIPDNS(IgnoreIP): + + def testIgnoreIPDNSOK(self): + self.filter.addIgnoreIP("www.epfl.ch") + self.assertTrue(self.filter.inIgnoreIPList("128.178.50.12")) + + def testIgnoreIPDNSNOK(self): # Test DNS self.filter.addIgnoreIP("www.epfl.ch") self.assertFalse(self.filter.inIgnoreIPList("127.177.50.10")) + self.assertFalse(self.filter.inIgnoreIPList("128.178.50.11")) + self.assertFalse(self.filter.inIgnoreIPList("128.178.50.13")) class LogFile(unittest.TestCase): @@ -194,11 +236,12 @@ class LogFile(unittest.TestCase): self.assertTrue(self.filter.isModified(LogFile.FILENAME)) -class LogFileMonitor(unittest.TestCase): +class LogFileMonitor(LogCaptureTestCase): """Few more tests for FilterPoll API """ def setUp(self): """Call before every test case.""" + LogCaptureTestCase.setUp(self) self.filter = self.name = 'NA' _, self.name = tempfile.mkstemp('fail2ban', 'monitorfailures') self.file = open(self.name, 'a') @@ -208,6 +251,7 @@ class LogFileMonitor(unittest.TestCase): self.filter.addFailRegex("(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) ") def tearDown(self): + LogCaptureTestCase.tearDown(self) _killfile(self.file, self.name) pass @@ -225,6 +269,21 @@ class LogFileMonitor(unittest.TestCase): # shorter wait time for not modified status return not self.isModified(0.4) + def testNoLogFile(self): + os.chmod(self.name, 0) + self.filter.getFailures(self.name) + self.assertTrue(self._is_logged('Unable to open %s' % self.name)) + + def testRemovingFailRegex(self): + self.filter.delFailRegex(0) + self.assertFalse(self._is_logged('Cannot remove regular expression. Index 0 is not valid')) + self.filter.delFailRegex(0) + self.assertTrue(self._is_logged('Cannot remove regular expression. Index 0 is not valid')) + + def testRemovingIgnoreRegex(self): + self.filter.delIgnoreRegex(0) + self.assertTrue(self._is_logged('Cannot remove regular expression. Index 0 is not valid')) + def testNewChangeViaIsModified(self): # it is a brand new one -- so first we think it is modified self.assertTrue(self.isModified()) @@ -306,7 +365,6 @@ class LogFileMonitor(unittest.TestCase): from threading import Lock -from dummyjail import DummyJail def get_monitor_failures_testcase(Filter_): """Generator of TestCase's for different filters/backends @@ -545,7 +603,13 @@ class GetFailures(unittest.TestCase): def tearDown(self): """Call after every test case.""" - + def testTail(self): + self.filter.addLogPath(LogFile.FILENAME, tail=True) + self.assertEqual(self.filter.getLogPath()[-1].getPos(), 1653) + self.filter.getLogPath()[-1].close() + self.assertEqual(self.filter.getLogPath()[-1].readline(), "") + self.filter.delLogPath(LogFile.FILENAME) + self.assertEqual(self.filter.getLogPath(),[]) def testGetFailures01(self, filename=None, failures=None): filename = filename or GetFailures.FILENAME_01 diff --git a/testcases/samplestestcase.py b/testcases/samplestestcase.py index d88be5e9..6b4d4530 100644 --- a/testcases/samplestestcase.py +++ b/testcases/samplestestcase.py @@ -123,7 +123,6 @@ def testSampleRegexsFactory(name): regexsUsed.add(failregex) - # TODO: Remove exception handling once all regexs have samples for failRegexIndex, failRegex in enumerate(self.filter.getFailRegex()): self.assertTrue( failRegexIndex in regexsUsed, diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index c93326a5..e6137d2f 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" import unittest, socket, time, tempfile, os, sys -from server.server import Server +from server.server import Server, logSys from server.jail import Jail from common.exceptions import UnknownJailException @@ -521,6 +521,35 @@ class TransmitterLogging(TransmitterBase): self.setGetTest("loglevel", "0", 0) self.setGetTestNOK("loglevel", "Bird") + def testFlushLogs(self): + self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "rolled over")) + try: + f, fn = tempfile.mkstemp("fail2ban.log") + os.close(f) + self.server.setLogLevel(2) + self.assertEqual(self.transm.proceed(["set", "logtarget", fn]), (0, fn)) + logSys.warn("Before file moved") + try: + f2, fn2 = tempfile.mkstemp("fail2ban.log") + os.close(f2) + os.rename(fn, fn2) + logSys.warn("After file moved") + self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "rolled over")) + logSys.warn("After flushlogs") + with open(fn2,'r') as f: + self.assertTrue(f.next().endswith("Before file moved\n")) + self.assertTrue(f.next().endswith("After file moved\n")) + self.assertRaises(StopIteration, f.next) + with open(fn,'r') as f: + self.assertTrue(f.next().endswith("After flushlogs\n")) + self.assertRaises(StopIteration, f.next) + finally: + os.remove(fn2) + finally: + os.remove(fn) + self.assertEqual(self.transm.proceed(["set", "logtarget", "STDERR"]), (0, "STDERR")) + self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "flushed")) + class JailTests(unittest.TestCase): diff --git a/testcases/utils.py b/testcases/utils.py index 643c9ad1..b048c8c6 100644 --- a/testcases/utils.py +++ b/testcases/utils.py @@ -22,8 +22,9 @@ __author__ = "Yaroslav Halchenko" __copyright__ = "Copyright (c) 2013 Yaroslav Halchenko" __license__ = "GPL" -import logging, os, re, tempfile, sys, time, traceback +import unittest, logging, os, re, tempfile, sys, time, traceback from os.path import basename, dirname +from StringIO import StringIO # # Following "traceback" functions are adopted from PyMVPA distributed @@ -105,3 +106,29 @@ def mtimesleep(): # no sleep now should be necessary since polling tracks now not only # mtime but also ino and size pass + +class LogCaptureTestCase(unittest.TestCase): + + def setUp(self): + + # For extended testing of what gets output into logging + # system, we will redirect it to a string + logSys = logging.getLogger("fail2ban") + + # Keep old settings + self._old_level = logSys.level + self._old_handlers = logSys.handlers + # Let's log everything into a string + self._log = StringIO() + logSys.handlers = [logging.StreamHandler(self._log)] + logSys.setLevel(getattr(logging, 'DEBUG')) + + def tearDown(self): + """Call after every test case.""" + # print "O: >>%s<<" % self._log.getvalue() + logSys = logging.getLogger("fail2ban") + logSys.handlers = self._old_handlers + logSys.level = self._old_level + + def _is_logged(self, s): + return s in self._log.getvalue()