diff --git a/ChangeLog b/ChangeLog index ebd511c0..8fe5f763 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,8 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests ----------- - Fixes: + Daniel Black & Marcel Dopita + * filter.d/apache-auth -- fixed and apache auth samples provide. closes #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 @@ -39,12 +41,15 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests 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 Christophe Carles & Daniel Black * filter.d/perdition.conf -- filter added - Enhancements: Daniel Black * filter.d/{asterisk,assp,dovecot,proftpd}.conf -- regex hardening and extra failure examples in sample logs + * filter.d/apache-auth - added expressions for mod_authz, mod_auth and + mod_auth_digest failures. Daniel Black & Georgiy Mernov & ftoppi & Мернов Георгий * filter.d/exim.conf -- regex hardening and extra failure examples in sample logs @@ -58,6 +63,8 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests enabled jail. Closes gh-63 * is now enforced to end with an alphanumeric * filter.d/roundcube-auth.conf -- anchored version + * date matching - for standard asctime formats prefer more detailed + first (thus use year if available) Alexander Dietrich * action.d/sendmail-common.conf -- added common sendmail settings file and made the sender display name configurable @@ -66,6 +73,13 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests user Zurd and Daniel Black * filter/named-refused - added refused on zone transfer + * filter.d/{courier{login,smtp},proftpd,sieve,wuftpd,xinetd} - General + regex impovements + * IMPORTANT: 'lighttpd-fastcgi' filter has been renamed to 'suhosin', which + will require changing in jail.{conf,local} if using this filter. + Zurd + * filter.d/postfix - add filter for VRFY failures. closes gh-322. + ver. 0.8.10 (2013/06/12) - wanna-be-secure ----------- diff --git a/MANIFEST b/MANIFEST index 5491c7d5..f637dca0 100644 --- a/MANIFEST +++ b/MANIFEST @@ -101,7 +101,7 @@ config/filter.d/couriersmtp.conf config/filter.d/cyrus-imap.conf config/filter.d/exim.conf config/filter.d/gssftpd.conf -config/filter.d/lighttpd-fastcgi.conf +config/filter.d/suhosin.conf config/filter.d/named-refused.conf config/filter.d/postfix.conf config/filter.d/proftpd.conf diff --git a/THANKS b/THANKS index f333c833..779ce602 100644 --- a/THANKS +++ b/THANKS @@ -30,6 +30,7 @@ Joël Bertrand Justin Shore Kévin Drapel kojiro +Marcel Dopita Mark Edgington Markus Hoffmann Marvin Rouge diff --git a/config/filter.d/apache-auth.conf b/config/filter.d/apache-auth.conf index ae3232f2..fa828b72 100644 --- a/config/filter.d/apache-auth.conf +++ b/config/filter.d/apache-auth.conf @@ -12,14 +12,42 @@ before = apache-common.conf [Definition] -# Option: failregex -# Notes.: regex to match the password failure 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 +# This filter matches the authorization failures of Apache. It takes the log messages +# from the modules in aaa that return HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED or +# HTTP_FORBIDDEN and not AUTH_GENERAL_ERROR or HTTP_INTERNAL_SERVER_ERROR. # -failregex = ^%(_apache_error_client)s user .* (authentication failure|not found|password mismatch)\s*$ +# An unauthorized response 401 is the first step for a browser to instigate authentication +# however apache doesn't log this as an error. Only subsequent errors are logged in the +# error log. +# +# Source: +# +# By searching the code in http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/aaa/* +# for ap_log_rerror(APLOG_MARK, APLOG_ERR and examining resulting return code should get +# all of these expressions. Lots of submodules like mod_authz_* return back to mod_authz_core +# to return the actual failure. +# +# See also: http://wiki.apache.org/httpd/ListOfErrors +# Expressions that don't have tests and aren't common. +# more be added with https://issues.apache.org/bugzilla/show_bug.cgi?id=55284 +# ^%(_apache_error_client)s (AH01778: )?user .*: nonce expired \([\d.]+ seconds old - max lifetime [\d.]+\) - sending new nonce\s*$ +# ^%(_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*$ +# +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*$ + ^%(_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 (AH0177[56]: )?(Digest: )?invalid nonce .* received - (length|hash) 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*$ + # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/apache-common.conf b/config/filter.d/apache-common.conf index cc35ae5f..134fad29 100644 --- a/config/filter.d/apache-common.conf +++ b/config/filter.d/apache-common.conf @@ -18,4 +18,4 @@ after = apache-common.local # 2.2: [Sat Jun 01 11:23:08 2013] [error] [client 1.2.3.4] # 2.4: [Thu Jun 27 11:55:44.569531 2013] [core:info] [pid 4101:tid 2992634688] [client 1.2.3.4:46652] # Reference: https://github.com/fail2ban/fail2ban/issues/268 -_apache_error_client = \[[^]]+\] \[(error|core:\S+)\]( \[pid \d+:\S+ \d+\])? \[client (:\d{1,5})?\]( \S+:)? +_apache_error_client = \[[^]]*\] \[(error|\S+:\S+)\]( \[pid \d+:\S+ \d+\])? \[client (:\d{1,5})?\] diff --git a/config/filter.d/apache-nohome.conf b/config/filter.d/apache-nohome.conf index 1347b10d..0eede317 100644 --- a/config/filter.d/apache-nohome.conf +++ b/config/filter.d/apache-nohome.conf @@ -19,7 +19,7 @@ before = apache-common.conf # per-domain log files. # Values: TEXT # -failregex = ^%(_apache_error_client)s File does not exist: .*/~.* +failregex = ^%(_apache_error_client)s (AH00128: )?File does not exist: .*/~.* # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/courierlogin.conf b/config/filter.d/courierlogin.conf index 20731e5d..f096325e 100644 --- a/config/filter.d/courierlogin.conf +++ b/config/filter.d/courierlogin.conf @@ -5,8 +5,17 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_daemon = (?:courier)?(?:imapd?|pop3d?)(?:login)?(?:-ssl)? + # Option: failregex # Notes.: regex to match the password failures messages in the logfile. The # host must be matched by a group named "host". The tag "" can @@ -14,7 +23,7 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = LOGIN FAILED, .*, ip=\[\]$ +failregex = ^%(__prefix_line)sLOGIN FAILED, user=.*, ip=\[\]$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/couriersmtp.conf b/config/filter.d/couriersmtp.conf index 6c0cf5ff..65ffa5d7 100644 --- a/config/filter.d/couriersmtp.conf +++ b/config/filter.d/couriersmtp.conf @@ -4,8 +4,17 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_daemon = courieresmtpd + # 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 @@ -13,7 +22,7 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = error,relay=,.*550 User unknown +failregex = ^%(__prefix_line)serror,relay=,.*: 550 User unknown\.$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/cyrus-imap.conf b/config/filter.d/cyrus-imap.conf index 758f75de..0ace92c1 100644 --- a/config/filter.d/cyrus-imap.conf +++ b/config/filter.d/cyrus-imap.conf @@ -4,8 +4,17 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_daemon = (?:cyrus/)?(?:imapd?|pop3d?) + # 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 @@ -13,10 +22,7 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = : badlogin: .*\[\] plaintext .*SASL\(-13\): authentication failure: checkpass failed$ - : badlogin: .*\[\] LOGIN \[SASL\(-13\): authentication failure: checkpass failed\]$ - : badlogin: .*\[\] (?:CRAM-MD5|NTLM) \[SASL\(-13\): authentication failure: incorrect (?:digest|NTLM) response\]$ - : badlogin: .*\[\] DIGEST-MD5 \[SASL\(-13\): authentication failure: client response doesn't match what we generated\]$ +failregex = ^%(__prefix_line)sbadlogin: \S+ ?\[\] \S+ .*?\[?SASL\(-13\): authentication failure: .*\]?$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/postfix.conf b/config/filter.d/postfix.conf index f92c3619..da981733 100644 --- a/config/filter.d/postfix.conf +++ b/config/filter.d/postfix.conf @@ -4,8 +4,17 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_daemon = postfix/smtpd + # 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 @@ -13,8 +22,9 @@ # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = reject: RCPT from (.*)\[\]: 554 - reject: RCPT from (.*)\[\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$ +failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[\]: 554 5\.7\.1 .*$ + ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$ + ^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[\]: 550 5\.1\.1 .*$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/proftpd.conf b/config/filter.d/proftpd.conf index 13080fcc..d3120216 100644 --- a/config/filter.d/proftpd.conf +++ b/config/filter.d/proftpd.conf @@ -13,6 +13,8 @@ before = common.conf [Definition] +_deamon = proftpd + # 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 @@ -20,10 +22,12 @@ before = common.conf # (?:::f{4,6}:)?(?P[\w\-.^_]+) # Values: TEXT # -failregex = ^ %(__hostname)s %(__daemon_re)s%(__pid_re)s %(__hostname)s \(\S+\[\]\)[: -]+ USER .*: no such user found from \S+ \[\S+\] to \S+:\S+ *$ - ^ %(__hostname)s %(__daemon_re)s%(__pid_re)s %(__hostname)s \(\S+\[\]\)[: -]+ USER .* \(Login failed\): .*$ - ^ %(__hostname)s %(__daemon_re)s%(__pid_re)s %(__hostname)s \(\S+\[\]\)[: -]+ SECURITY VIOLATION: .* login attempted\. *$ - ^ %(__hostname)s %(__daemon_re)s%(__pid_re)s %(__hostname)s \(\S+\[\]\)[: -]+ Maximum login attempts \(\d+\) exceeded *$ + +__suffix_failed_login = (User not authorized for login|No such user found|Incorrect password|Password expired|Account disabled|Invalid shell: '\S+'|User in \S+|Limit (access|configuration) denies login|Not a UserAlias|maximum login length exceeded).? +failregex = ^%(__prefix_line)s%(__hostname)s \(\S+\[\]\)[: -]+ USER .*: no such user found from \S+ \[\S+\] to \S+:\S+ *$ + ^%(__prefix_line)s%(__hostname)s \(\S+\[\]\)[: -]+ USER .* \(Login failed\): %(__suffix_failed_login)s\s*$ + ^%(__prefix_line)s%(__hostname)s \(\S+\[\]\)[: -]+ SECURITY VIOLATION: .* login attempted\. *$ + ^%(__prefix_line)s%(__hostname)s \(\S+\[\]\)[: -]+ Maximum login attempts \(\d+\) exceeded *$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/sieve.conf b/config/filter.d/sieve.conf index 866b4228..b2af6774 100644 --- a/config/filter.d/sieve.conf +++ b/config/filter.d/sieve.conf @@ -4,15 +4,24 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_deamon = (?:cyrus/)?(?:tim)?sieved? + # 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. # Values: TEXT # -failregex = : badlogin: .*\[\] (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failure$ +failregex = ^%(__prefix_line)sbadlogin: \S+ ?\[\] \S+ authentication failure$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/lighttpd-fastcgi.conf b/config/filter.d/suhosin.conf similarity index 67% rename from config/filter.d/lighttpd-fastcgi.conf rename to config/filter.d/suhosin.conf index 1c6e3fce..c79c157f 100644 --- a/config/filter.d/lighttpd-fastcgi.conf +++ b/config/filter.d/suhosin.conf @@ -9,7 +9,8 @@ # Notes.: regex to match ALERTS as notified by lighttpd's FastCGI Module # Values: TEXT # -failregex = .*ALERT\ -\ .*attacker\ \'\' +# https://github.com/stefanesser/suhosin/blob/1fba865ab73cc98a3109f88d85eb82c1bfc29b37/log.c#L161 +failregex = ALERT - .* \(attacker '', file '.*'(?:, line \d+)?\)$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/wuftpd.conf b/config/filter.d/wuftpd.conf index 3351d258..de98d02d 100644 --- a/config/filter.d/wuftpd.conf +++ b/config/filter.d/wuftpd.conf @@ -4,14 +4,21 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + [Definition] +_daemon = wu-ftpd + # Option: failregex # Notes.: regex to match the password failures messages in the logfile. # Values: TEXT # -failregex = wu-ftpd(?:\[\d+\])?:\s+\(pam_unix\)\s+authentication failure.* rhost=$ - wu-ftpd(?:\[\d+\])?: *failed login from .*\[\] *$ +failregex = ^%(__prefix_line)sfailed login from \S+ \[\]\s*$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/filter.d/xinetd-fail.conf b/config/filter.d/xinetd-fail.conf index 4ff5bfde..253ce15d 100644 --- a/config/filter.d/xinetd-fail.conf +++ b/config/filter.d/xinetd-fail.conf @@ -4,8 +4,17 @@ # # +[INCLUDES] + +# Read common prefixes. If any customizations available -- read them from +# common.local +before = common.conf + + [Definition] +_daemon = xinetd + # 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 @@ -19,8 +28,8 @@ # load => xinetd: max_load (temporary problem) # -failregex = xinetd(?:\[\d{1,5}\])?: FAIL: \S+ address from=$ - xinetd(?:\[\d{1,5}\])?: FAIL: \S+ libwrap from=$ +failregex = ^%(__prefix_line)sFAIL: \S+ address from=$ + ^%(__prefix_line)sFAIL: \S+ libwrap from=$ # Option: ignoreregex # Notes.: regex to ignore. If this regex matches, the line is ignored. diff --git a/config/jail.conf b/config/jail.conf index ba49ed51..86c61911 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -256,25 +256,15 @@ filter = php-url-fopen logpath = /var/www/*/logs/access_log maxretry = 1 -# A simple PHP-fastcgi jail which works with lighttpd. -# If you run a lighttpd server, then you probably will -# find these kinds of messages in your error_log: -# ALERT – tried to register forbidden variable ‘GLOBALS’ -# through GET variables (attacker '1.2.3.4', file '/var/www/default/htdocs/index.php') -# This jail would block the IP 1.2.3.4. - -[lighttpd-fastcgi] +[suhosin] enabled = false -filter = lighttpd-fastcgi -action = iptables-multiport[name=lighttpd-fastcgi, port="http,https"] +filter = suhosin +action = iptables-multiport[name=suhosin, port="http,https"] # adapt the following two items as needed logpath = /var/log/lighttpd/error.log maxretry = 2 -# Same as above for mod_auth -# It catches wrong authentications - [lighttpd-auth] enabled = false diff --git a/server/datedetector.py b/server/datedetector.py index 0bbbc089..ab2dd174 100644 --- a/server/datedetector.py +++ b/server/datedetector.py @@ -46,13 +46,13 @@ class DateDetector: def addDefaultTemplate(self): self.__lock.acquire() try: - # standard + # asctime with subsecond template = DateStrptime() - template.setName("MONTH Day Hour:Minute:Second") - template.setRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") - template.setPattern("%b %d %H:%M:%S") + template.setName("WEEKDAY MONTH Day Hour:Minute:Second[.subsecond] Year") + template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}\.\d+ \d{4}") + template.setPattern("%a %b %d %H:%M:%S.%f %Y") self._appendTemplate(template) - # asctime + # asctime without no subsecond template = DateStrptime() template.setName("WEEKDAY MONTH Day Hour:Minute:Second Year") template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}") @@ -64,6 +64,12 @@ class DateDetector: template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") template.setPattern("%a %b %d %H:%M:%S") self._appendTemplate(template) + # standard - most loose from above 3 so by default follows after + template = DateStrptime() + template.setName("MONTH Day Hour:Minute:Second") + template.setRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") + template.setPattern("%b %d %H:%M:%S") + self._appendTemplate(template) # simple date template = DateStrptime() template.setName("Year/Month/Day Hour:Minute:Second") diff --git a/testcases/files/config/apache-auth/README b/testcases/files/config/apache-auth/README new file mode 100644 index 00000000..b25a30b2 --- /dev/null +++ b/testcases/files/config/apache-auth/README @@ -0,0 +1,13 @@ + +Apache Auth. + +This directory contains the configuration file of Apache's Web Server to +simulate authentication files. + +These assumed that /var/www/html is the web root and AllowOverides is "All". + +The subdirectories here are copied to the /var/www/html directory. + +Commands executed are in testcases/files/log/apache-auth with their +corresponding failure mechanism. + diff --git a/testcases/files/config/apache-auth/basic/authz_owner/.htaccess b/testcases/files/config/apache-auth/basic/authz_owner/.htaccess new file mode 100644 index 00000000..583e5422 --- /dev/null +++ b/testcases/files/config/apache-auth/basic/authz_owner/.htaccess @@ -0,0 +1,5 @@ +AuthType basic +AuthName "private area" +AuthBasicProvider file +AuthUserFile /var/www/html/basic/authz_owner/.htpasswd +Require file-owner diff --git a/testcases/files/config/apache-auth/basic/authz_owner/.htpasswd b/testcases/files/config/apache-auth/basic/authz_owner/.htpasswd new file mode 100644 index 00000000..d9de6185 --- /dev/null +++ b/testcases/files/config/apache-auth/basic/authz_owner/.htpasswd @@ -0,0 +1 @@ +username:$apr1$1f5oQUl4$21lLXSN7xQOPtNsj5s4Nk/ diff --git a/testcases/files/config/apache-auth/basic/authz_owner/cant_get_me.html b/testcases/files/config/apache-auth/basic/authz_owner/cant_get_me.html new file mode 100644 index 00000000..e69de29b diff --git a/testcases/files/config/apache-auth/basic/file/.htaccess b/testcases/files/config/apache-auth/basic/file/.htaccess new file mode 100644 index 00000000..e36e884b --- /dev/null +++ b/testcases/files/config/apache-auth/basic/file/.htaccess @@ -0,0 +1,5 @@ +AuthType basic +AuthName "private area" +AuthBasicProvider file +AuthUserFile /var/www/html/basic/file/.htpasswd +Require valid-user diff --git a/testcases/files/config/apache-auth/basic/file/.htpasswd b/testcases/files/config/apache-auth/basic/file/.htpasswd new file mode 100644 index 00000000..fcc6ec72 --- /dev/null +++ b/testcases/files/config/apache-auth/basic/file/.htpasswd @@ -0,0 +1 @@ +username:$apr1$uUMsOjCQ$.BzXClI/B/vZKddgIAJCR. diff --git a/testcases/files/config/apache-auth/digest.py b/testcases/files/config/apache-auth/digest.py new file mode 100755 index 00000000..bed4067c --- /dev/null +++ b/testcases/files/config/apache-auth/digest.py @@ -0,0 +1,159 @@ +#!/bin/env python +import requests +import md5 + + +def auth(v): + + ha1 = md5.new(username + ':' + realm + ':' + password).hexdigest() + ha2 = md5.new("GET:" + url).hexdigest() + + #response = md5.new(ha1 + ':' + v['nonce'][1:-1] + ':' + v['nc'] + ':' + v['cnonce'][1:-1] + # + ':' + v['qop'][1:-1] + ':' + ha2).hexdigest() + + nonce = v['nonce'][1:-1] + nc=v.get('nc') or '' + cnonce = v.get('cnonce') or '' + opaque = v.get('opaque') or '' + qop = v['qop'][1:-1] + algorithm = v['algorithm'] + response = md5.new(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).hexdigest() + + p = requests.Request('GET', host + url).prepare() + #p.headers['Authentication-Info'] = response + p.headers['Authorization'] = """ + Digest username="%s", + algorithm="%s", + realm="%s", + uri="%s", + nonce="%s", + cnonce="", + nc="", + qop=%s, + response="%s" + """ % ( username, algorithm, realm, url, nonce, qop, response ) +# opaque="%s", + print p.method, p.url, p.headers + s = requests.Session() + return s.send(p) + +def preauth(): + r = requests.get(host + url) + print r + r.headers['www-authenticate'].split(', ') + return dict([ a.split('=',1) for a in r.headers['www-authenticate'].split(', ') ]) + + +url='/digest/' +host = 'http://localhost:801' + +v = preauth() + +username="username" +password = "password" +print v + +realm = 'so far away' +r = auth(v) + +realm = v['Digest realm'][1:-1] + +# [Sun Jul 28 21:27:56.549667 2013] [auth_digest:error] [pid 24835:tid 139895297222400] [client 127.0.0.1:57052] AH01788: realm mismatch - got `so far away' but expected `digest private area' + + +algorithm = v['algorithm'] +v['algorithm'] = 'super funky chicken' +r = auth(v) + +# [Sun Jul 28 21:41:20 2013] [error] [client 127.0.0.1] Digest: unknown algorithm `super funky chicken' received: /digest/ + +print r.status_code,r.headers, r.text +v['algorithm'] = algorithm + + +r = auth(v) +print r.status_code,r.headers, r.text + +nonce = v['nonce'] +v['nonce']=v['nonce'][5:-5] + +r = auth(v) +print r.status_code,r.headers, r.text + +# [Sun Jul 28 21:05:31.178340 2013] [auth_digest:error] [pid 24224:tid 139895539455744] [client 127.0.0.1:56906] AH01793: invalid qop `auth' received: /digest/qop_none/ + + +v['nonce']=nonce[0:11] + 'ZZZ' + nonce[14:] + +r = auth(v) +print r.status_code,r.headers, r.text + +#[Sun Jul 28 21:18:11.769228 2013] [auth_digest:error] [pid 24752:tid 139895505884928] [client 127.0.0.1:56964] AH01776: invalid nonce b9YAiJDiBAZZZ1b1abe02d20063ea3b16b544ea1b0d981c1bafe received - hash is not d42d824dee7aaf50c3ba0a7c6290bd453e3dd35b + + +url='/digest_time/' +v=preauth() + +import time +time.sleep(1) + +r = auth(v) +print r.status_code,r.headers, r.text + +# Obtained by putting the following code in modules/aaa/mod_auth_digest.c +# in the function initialize_secret +# { +# const char *hex = "0123456789abcdef"; +# char secbuff[SECRET_LEN * 4]; +# char *hash = secbuff; +# int idx; + +# for (idx=0; idx> 4]; +# *hash++ = hex[secret[idx] & 0xF]; +# } +# *hash = '\0'; +# /* remove comment makings in below for apache-2.4+ */ +# ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, /* APLOGNO(11759) */ "secret: %s", secbuff); +# } + + +import sha +import binascii +import base64 +import struct + +apachesecret = binascii.unhexlify('497d8894adafa5ec7c8c981ddf9c8457da7a90ac') +s = sha.sha(apachesecret) + +v=preauth() + +print v['nonce'] +realm = v['Digest realm'][1:-1] + +(t,) = struct.unpack('l',base64.b64decode(v['nonce'][1:13])) + +# whee, time travel +t = t + 5540 + +timepac = base64.b64encode(struct.pack('l',t)) + +s.update(realm) +s.update(timepac) + +v['nonce'] = v['nonce'][0] + timepac + s.hexdigest() + v['nonce'][-1] + +print v + +r = auth(v) +#[Mon Jul 29 02:12:55.539813 2013] [auth_digest:error] [pid 9647:tid 139895522670336] [client 127.0.0.1:58474] AH01777: invalid nonce 59QJppTiBAA=b08983fd166ade9840407df1b0f75b9e6e07d88d received - user attempted time travel +print r.status_code,r.headers, r.text + +url='/digest_onetime/' +v=preauth() + +# Need opaque header handling in auth +r = auth(v) +print r.status_code,r.headers, r.text +r = auth(v) +print r.status_code,r.headers, r.text diff --git a/testcases/files/config/apache-auth/digest/.htaccess b/testcases/files/config/apache-auth/digest/.htaccess new file mode 100644 index 00000000..c4d0d003 --- /dev/null +++ b/testcases/files/config/apache-auth/digest/.htaccess @@ -0,0 +1,6 @@ +AuthType Digest +AuthName "digest private area" +AuthDigestDomain /digest/ +AuthBasicProvider file +AuthUserFile /var/www/html/digest/.htpasswd +Require valid-user diff --git a/testcases/files/config/apache-auth/digest/.htpasswd b/testcases/files/config/apache-auth/digest/.htpasswd new file mode 100644 index 00000000..cc649515 --- /dev/null +++ b/testcases/files/config/apache-auth/digest/.htpasswd @@ -0,0 +1 @@ +username:digest private area:fad48d3a7c63f61b5b3567a4105bbb04 diff --git a/testcases/files/config/apache-auth/digest_anon/.htaccess b/testcases/files/config/apache-auth/digest_anon/.htaccess new file mode 100644 index 00000000..c8e8648e --- /dev/null +++ b/testcases/files/config/apache-auth/digest_anon/.htaccess @@ -0,0 +1,9 @@ +AuthType Digest +AuthName "digest anon" +AuthDigestDomain /digest_anon/ +AuthBasicProvider file anon +AuthUserFile /var/www/html/digest_anon/.htpasswd +Anonymous_NoUserID off +Anonymous anonymous +Anonymous_LogEmail on +Require valid-user diff --git a/testcases/files/config/apache-auth/digest_anon/.htpasswd b/testcases/files/config/apache-auth/digest_anon/.htpasswd new file mode 100644 index 00000000..47a6af58 --- /dev/null +++ b/testcases/files/config/apache-auth/digest_anon/.htpasswd @@ -0,0 +1,3 @@ +username:digest anon:25e4077a9344ceb1a88f2a62c9fb60d8 +05bbb04 +anonymous:digest anon:faa4e5870970cf935bb9674776e6b26a diff --git a/testcases/files/config/apache-auth/digest_time/.htaccess b/testcases/files/config/apache-auth/digest_time/.htaccess new file mode 100644 index 00000000..44036f57 --- /dev/null +++ b/testcases/files/config/apache-auth/digest_time/.htaccess @@ -0,0 +1,7 @@ +AuthType Digest +AuthName "digest private area" +AuthDigestDomain /digest_time/ +AuthBasicProvider file +AuthUserFile /var/www/html/digest_time/.htpasswd +AuthDigestNonceLifetime 1 +Require valid-user diff --git a/testcases/files/config/apache-auth/digest_time/.htpasswd b/testcases/files/config/apache-auth/digest_time/.htpasswd new file mode 100644 index 00000000..cc649515 --- /dev/null +++ b/testcases/files/config/apache-auth/digest_time/.htpasswd @@ -0,0 +1 @@ +username:digest private area:fad48d3a7c63f61b5b3567a4105bbb04 diff --git a/testcases/files/config/apache-auth/digest_wrongrelm/.htaccess b/testcases/files/config/apache-auth/digest_wrongrelm/.htaccess new file mode 100644 index 00000000..5e7946d2 --- /dev/null +++ b/testcases/files/config/apache-auth/digest_wrongrelm/.htaccess @@ -0,0 +1,6 @@ +AuthType Digest +AuthName "digest private area" +AuthDigestDomain /digest_wrongrelm/ +AuthBasicProvider file +AuthUserFile /var/www/html/digest_wrongrelm/.htpasswd +Require valid-user diff --git a/testcases/files/config/apache-auth/digest_wrongrelm/.htpasswd b/testcases/files/config/apache-auth/digest_wrongrelm/.htpasswd new file mode 100644 index 00000000..019e005d --- /dev/null +++ b/testcases/files/config/apache-auth/digest_wrongrelm/.htpasswd @@ -0,0 +1,2 @@ +username:wrongrelm:99cd340e1283c6d0ab34734bd47bdc30 +4105bbb04 diff --git a/testcases/files/config/apache-auth/noentry/.htaccess b/testcases/files/config/apache-auth/noentry/.htaccess new file mode 100644 index 00000000..3a428827 --- /dev/null +++ b/testcases/files/config/apache-auth/noentry/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/testcases/files/logs/apache-auth b/testcases/files/logs/apache-auth index 6035db3f..b2aa75ff 100644 --- a/testcases/files/logs/apache-auth +++ b/testcases/files/logs/apache-auth @@ -3,5 +3,114 @@ [Sat Jun 01 02:17:42 2013] [error] [client 192.168.33.1] File does not exist: /srv/http/site/[client 192.168.0.1] user root not found # should match -# failJSON: { "time": "2005-06-01T02:17:42", "match": true , "host": "192.168.0.2" } +# from https://github.com/fail2ban/fail2ban/issues/286 +# failJSON: { "time": "2013-07-11T01:21:41", "match": true , "host": "194.228.20.113" } +[Thu Jul 11 01:21:41 2013] [error] [client 194.228.20.113] user not found: / + +# failJSON: { "time": "2013-07-11T01:21:43", "match": true , "host": "194.228.20.113" } +[Thu Jul 11 01:21:43 2013] [error] [client 194.228.20.113] user dsfasdf not found: / + +# The failures below use the configuration described in testcases/files/config/apache-auth +# + +# wget http://localhost/noentry/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-17T23:20:45", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 23:20:45 2013] [error] [client 127.0.0.1] client denied by server configuration: /var/www/html/noentry/cant_get_me.html + +# failJSON: { "time": "2013-07-20T21:34:49", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:34:49.453232 2013] [access_compat:error] [pid 17512:tid 140123104306944] [client 127.0.0.1:51380] AH01797: client denied by server configuration: /var/www/html/noentry/cant_get_me.html + +# wget --http-user='' --http-password='' http://localhost/basic/file/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-17T23:14:37", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 23:14:37 2013] [error] [client 127.0.0.1] user not found: /basic/anon/cant_get_me.html + +# failJSON: { "time": "2013-07-20T21:37:32", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:37:32.266605 2013] [auth_basic:error] [pid 17512:tid 140123079128832] [client 127.0.0.1:51386] AH01618: user not found: /basic/file/cant_get_me.html + +# wget --http-user=username --http-password=wrongpass http://localhost/basic/file -O /dev/null +# failJSON: { "time": "2013-07-17T22:18:52", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 22:18:52 2013] [error] [client 127.0.0.1] user username: authentication failure for "/basic/file": Password Mismatch + +# failJSON: { "time": "2013-07-20T21:39:11", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:39:11.978080 2013] [auth_basic:error] [pid 17512:tid 140123053950720] [client 127.0.0.1:51390] AH01617: user username: authentication failure for "/basic/file": Password Mismatch + +# wget --http-user=wrongusername --http-password=wrongpass http://localhost/basic/file -O /dev/null +# failJSON: { "time": "2013-07-17T22:32:48", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 22:32:48 2013] [error] [client 127.0.0.1] user wrongusername not found: /basic/file + +# failJSON: { "time": "2013-07-20T21:40:33", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:40:33.803528 2013] [auth_basic:error] [pid 17540:tid 140123095914240] [client 127.0.0.1:51395] AH01618: user wrongusername not found: /basic/file + +# wget --header='Authorization: Digest username="Mufasa",realm="testrealm@host.com",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",uri="/dir/index.html",qop=auth,nc=00000001,cnonce="0a4f113b",response="6629fae49393a05397450978507c4ef1",opaque="5ccc069c403ebaf9f0171e9517f40e41"' http://localhost/basic/file -O /dev/null +# failJSON: { "time": "2013-07-17T22:39:55", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 22:39:55 2013] [error] [client 127.0.0.1] client used wrong authentication scheme: /basic/file + +# failJSON: { "time": "2013-07-20T21:41:52", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:41:52.523931 2013] [auth_basic:error] [pid 17512:tid 140122964092672] [client 127.0.0.1:51396] AH01614: client used wrong authentication scheme: /basic/file + +# wget --http-user=username --http-password=password http://localhost/basic/authz_owner/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-17T22:54:32", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 22:54:32 2013] [error] [client 127.0.0.1] Authorization of user username to access /basic/authz_owner/cant_get_me.html failed, reason: file owner dan does not match. + +# failJSON: { "time": "2013-07-20T22:11:43", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 22:11:43.147674 2013] [authz_owner:error] [pid 17540:tid 140122922129152] [client 127.0.0.1:51548] AH01637: Authorization of user username to access /basic/authz_owner/cant_get_me.html failed, reason: file owner dan does not match + +# wget --http-user=username --http-password=password http://localhost/basic/authz_owner/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-20T21:42:44", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:42:44.304159 2013] [authz_core:error] [pid 17484:tid 140123095914240] [client 127.0.0.1:51397] AH01631: user username: authorization failure for "/basic/authz_owner/cant_get_me.html": + +# wget --http-user='username' --http-password='wrongpassword' http://localhost/digest/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-17T23:50:37", "match": true , "host": "127.0.0.1" } +[Wed Jul 17 23:50:37 2013] [error] [client 127.0.0.1] Digest: user username: password mismatch: /digest/cant_get_me.html + +# failJSON: { "time": "2013-07-20T21:44:06", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:44:06.867985 2013] [auth_digest:error] [pid 17540:tid 140123070736128] [client 127.0.0.1:51406] AH01792: user username: password mismatch: /digest/cant_get_me.html + +# wget --http-user='username' --http-password='password' http://localhost/digest_wrongrelm/cant_get_me.html -O /dev/null +# failJSON: { "time": "2013-07-18T00:08:39", "match": true , "host": "127.0.0.1" } +[Thu Jul 18 00:08:39 2013] [error] [client 127.0.0.1] Digest: user `username' in realm `digest private area' not found: /digest_wrongrelm/cant_get_me.html + +# failJSON: { "time": "2013-07-20T21:45:28", "match": true , "host": "127.0.0.1" } +[Sat Jul 20 21:45:28.890523 2013] [auth_digest:error] [pid 17540:tid 140122972485376] [client 127.0.0.1:51408] AH01790: user `username' in realm `digest private area' not found: /digest_wrongrelm/cant_get_me.html + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:05:31", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:05:31.178340 2013] [auth_digest:error] [pid 24224:tid 139895539455744] [client 127.0.0.1:56906] AH01793: invalid qop `auth' received: /digest/qop_none/ + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:12:44", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:12:44 2013] [error] [client 127.0.0.1] Digest: invalid nonce JDiBAA=db9372522295196b7ac31db99e10cd1106c received - length is not 52 + + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:16:37", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:16:37 2013] [error] [client 127.0.0.1] Digest: invalid nonce l19lgpDiBAZZZf1ec3d9613f3b3ef43660e3628d78455fd8b937 received - hash is not 6fda8bbcbcf85ff1ebfe7d1c43faba583bc53a02 + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:18:11", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:18:11.769228 2013] [auth_digest:error] [pid 24752:tid 139895505884928] [client 127.0.0.1:56964] AH01776: invalid nonce b9YAiJDiBAZZZ1b1abe02d20063ea3b16b544ea1b0d981c1bafe received - hash is not d42d824dee7aaf50c3ba0a7c6290bd453e3dd35b + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:30:02", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:30:02 2013] [error] [client 127.0.0.1] Digest: realm mismatch - got `so far away' but expected `digest private area' + +# failJSON: { "time": "2013-07-28T21:27:56", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:27:56.549667 2013] [auth_digest:error] [pid 24835:tid 139895297222400] [client 127.0.0.1:57052] AH01788: realm mismatch - got `so far away' but expected `digest private area' + + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-28T21:41:20", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:41:20 2013] [error] [client 127.0.0.1] Digest: unknown algorithm `super funky chicken' received: /digest/ + +# failJSON: { "time": "2013-07-28T21:42:03", "match": true , "host": "127.0.0.1" } +[Sun Jul 28 21:42:03.930190 2013] [auth_digest:error] [pid 24835:tid 139895505884928] [client 127.0.0.1:57115] AH01789: unknown algorithm `super funky chicken' received: /digest/ + +# ./testcases/files/config/apache-auth/digest.py +# failJSON: { "time": "2013-07-29T02:15:26", "match": true , "host": "127.0.0.1" } +[Mon Jul 29 02:15:26 2013] [error] [client 127.0.0.1] Digest: invalid nonce LWEDr5TiBAA=ceddd011628c30e3646f7acda4f1a0ab6b7c5ae6 received - user attempted time travel + +# failJSON: { "time": "2013-07-29T02:12:55", "match": true , "host": "127.0.0.1" } +[Mon Jul 29 02:12:55.539813 2013] [auth_digest:error] [pid 9647:tid 139895522670336] [client 127.0.0.1:58474] AH01777: invalid nonce 59QJppTiBAA=b08983fd166ade9840407df1b0f75b9e6e07d88d received - user attempted time travel + +# 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 diff --git a/testcases/files/logs/apache-badbots b/testcases/files/logs/apache-badbots new file mode 100644 index 00000000..35669252 --- /dev/null +++ b/testcases/files/logs/apache-badbots @@ -0,0 +1,2 @@ +# failJSON: { "time": "2007-03-05T14:39:21", "match": true , "host": "1.2.3.4" } +1.2.3.4 - - [05/Mar/2007:14:39:21 +0100] "POST /123.html/trackback/ HTTP/1.0" 301 459 "http://www.mydomain.tld/123.html/trackback" "TrackBack/1.02" diff --git a/testcases/files/logs/apache-nohome b/testcases/files/logs/apache-nohome index aea0d816..78327a62 100644 --- a/testcases/files/logs/apache-nohome +++ b/testcases/files/logs/apache-nohome @@ -1,6 +1,6 @@ # Apache 2.2 -# failJSON: { "time": "2005-06-01T11:23:08", "match": true , "host": "1.2.3.4" } +# failJSON: { "time": "2013-06-01T11:23:08", "match": true , "host": "1.2.3.4" } [Sat Jun 01 11:23:08 2013] [error] [client 1.2.3.4] File does not exist: /xxx/~ # Apache 2.4 -# failJSON: { "time": "2005-06-27T11:55:44", "match": true , "host": "192.0.2.12" } +# failJSON: { "time": "2013-06-27T11:55:44", "match": true , "host": "192.0.2.12" } [Thu Jun 27 11:55:44.569531 2013] [core:info] [pid 4101:tid 2992634688] [client 192.0.2.12:46652] AH00128: File does not exist: /xxx/~ diff --git a/testcases/files/logs/apache-noscript b/testcases/files/logs/apache-noscript index e08b3468..53e33baf 100644 --- a/testcases/files/logs/apache-noscript +++ b/testcases/files/logs/apache-noscript @@ -1,2 +1,4 @@ -# failJSON: { "time": "2005-06-09T07:57:47", "match": true , "host": "192.0.43.10" } +# failJSON: { "time": "2013-06-09T07:57:47", "match": true , "host": "192.0.43.10" } [Sun Jun 09 07:57:47 2013] [error] [client 192.0.43.10] script '/usr/lib/cgi-bin/gitweb.cgiwp-login.php' not found or unable to stat +# failJSON: { "time": "2008-07-22T06:48:30", "match": true , "host": "198.51.100.86" } +[Tue Jul 22 06:48:30 2008] [error] [client 198.51.100.86] File does not exist: /home/southern/public_html/azenv.php diff --git a/testcases/files/logs/apache-overflows b/testcases/files/logs/apache-overflows index 1af377f1..d40c1c4f 100644 --- a/testcases/files/logs/apache-overflows +++ b/testcases/files/logs/apache-overflows @@ -1,4 +1,4 @@ -# failJSON: { "time": "2005-03-16T15:39:29", "match": true , "host": "58.179.109.179" } +# 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": "2005-03-15T15:44:47", "match": true , "host": "121.222.2.133" } +# 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 diff --git a/testcases/files/logs/courierlogin b/testcases/files/logs/courierlogin new file mode 100644 index 00000000..e3d0d8c3 --- /dev/null +++ b/testcases/files/logs/courierlogin @@ -0,0 +1,8 @@ +# failJSON: { "time": "2005-04-23T21:59:01", "match": true , "host": "1.2.3.4" } +Apr 23 21:59:01 dns2 imapd: LOGIN FAILED, user=sales@example.com, ip=[::ffff:1.2.3.4] +# failJSON: { "time": "2005-04-23T21:59:38", "match": true , "host": "198.51.100.76" } +Apr 23 21:59:38 dns2 pop3d: LOGIN FAILED, user=info@example.com, ip=[::ffff:198.51.100.76] +# failJSON: { "time": "2004-11-13T08:11:53", "match": true , "host": "198.51.100.33" } +Nov 13 08:11:53 server imapd-ssl: LOGIN FAILED, user=user@domain.tld, ip=[::ffff:198.51.100.33] +# failJSON: { "time": "2005-04-17T19:17:11", "match": true , "host": "1.2.3.4" } +Apr 17 19:17:11 SERVER courierpop3login: LOGIN FAILED, user=USER@EXAMPLE.org, ip=[::ffff:1.2.3.4] diff --git a/testcases/files/logs/couriersmtp b/testcases/files/logs/couriersmtp new file mode 100644 index 00000000..212df3b4 --- /dev/null +++ b/testcases/files/logs/couriersmtp @@ -0,0 +1,8 @@ +# failJSON: { "time": "2005-04-10T03:47:57", "match": true , "host": "1.2.3.4" } +Apr 10 03:47:57 web courieresmtpd: error,relay=::ffff:1.2.3.4,ident=tmf,from=,to=: 550 User unknown. +# failJSON: { "time": "2005-07-06T03:42:28", "match": true , "host": "1.2.3.4" } +Jul 6 03:42:28 whistler courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=: 550 User unknown. +# failJSON: { "time": "2004-11-21T23:16:17", "match": true , "host": "1.2.3.4" } +Nov 21 23:16:17 server courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=<>: 550 User unknown. +# failJSON: { "time": "2004-08-14T12:51:04", "match": true , "host": "1.2.3.4" } +Aug 14 12:51:04 HOSTNAME courieresmtpd: error,relay=::ffff:1.2.3.4,from=,to=: 550 User unknown. diff --git a/testcases/files/logs/cyrus-imap b/testcases/files/logs/cyrus-imap new file mode 100644 index 00000000..9bf271f6 --- /dev/null +++ b/testcases/files/logs/cyrus-imap @@ -0,0 +1,13 @@ +# failJSON: { "time": "2005-01-04T21:51:05", "match": true , "host": "127.0.0.1" } +Jan 4 21:51:05 hostname cyrus/imap[5355]: badlogin: localhost.localdomain [127.0.0.1] plaintext cyrus@localdomain SASL(-13): authentication failure: checkpass failed +# failJSON: { "time": "2005-02-20T17:23:32", "match": true , "host": "198.51.100.23" } +Feb 20 17:23:32 domain cyrus/pop3[18635]: badlogin: localhost [198.51.100.23] plaintext administrator SASL(-13): authentication failure: checkpass failed +# failJSON: { "time": "2005-02-20T17:23:32", "match": true , "host": "1.2.3.4" } +Feb 20 17:23:32 cyrus/pop3[4297]: badlogin: example.com [1.2.3.4] plaintext mail0001 SASL(-13): authentication failure: checkpass failed +# failJSON: { "time": "2005-06-08T18:11:13", "match": true , "host": "198.51.100.45" } +Jun 8 18:11:13 lampserver imap[4480]: badlogin: example.com [198.51.100.45] DIGEST-MD5 [SASL(-13): authentication failure: client response doesn't match what we generated] +# failJSON: { "time": "2004-12-21T10:01:57", "match": true , "host": "198.51.100.57" } +Dec 21 10:01:57 hostname imapd[18454]: badlogin: example.com [198.51.100.57] CRAM-MD5 [SASL(-13): authentication failure: incorrect digest response] +# failJSON: { "time": "2004-12-30T16:03:27", "match": true , "host": "1.2.3.4" } +Dec 30 16:03:27 somehost imapd[2517]: badlogin: local-somehost[1.2.3.4] OTP [SASL(-13): authentication failure: External SSF not good enough] + diff --git a/testcases/files/logs/dropbear b/testcases/files/logs/dropbear index 414c3c7a..d8a4d4d3 100644 --- a/testcases/files/logs/dropbear +++ b/testcases/files/logs/dropbear @@ -1,3 +1,12 @@ +# failJSON: { "time": "2005-03-24T15:25:51", "match": true , "host": "198.51.100.87" } +Mar 24 15:25:51 buffalo1 dropbear[4092]: bad password attempt for 'root' from 198.51.100.87:5543 +# failJSON: { "time": "2005-02-11T15:23:17", "match": true , "host": "198.51.100.215" } +Feb 11 15:23:17 dropbear[1252]: login attempt for nonexistent user from ::ffff:198.51.100.215:60495 +# failJSON: { "time": "2005-03-24T15:25:51", "match": true , "host": "198.51.100.87" } +Mar 24 15:25:51 buffalo1 dropbear[4092]: bad password attempt for 'root' from 198.51.100.87:5543 +# failJSON: { "time": "2005-02-11T15:23:17", "match": true , "host": "198.51.100.215" } +Feb 11 15:23:17 dropbear[1252]: login attempt for nonexistent user from ::ffff:198.51.100.215:60495 + # failJSON: { "time": "2005-07-27T01:04:12", "match": true , "host": "1.2.3.4" } Jul 27 01:04:12 fail2ban-test dropbear[1335]: Bad password attempt for 'root' from 1.2.3.4:60588 # failJSON: { "time": "2005-07-27T01:04:22", "match": true , "host": "1.2.3.4" } diff --git a/testcases/files/logs/gssftpd b/testcases/files/logs/gssftpd new file mode 100644 index 00000000..58fda7dc --- /dev/null +++ b/testcases/files/logs/gssftpd @@ -0,0 +1,2 @@ +# failJSON: { "time": "2005-01-22T18:09:46", "match": true , "host": "198.51.100.23" } +Jan 22 18:09:46 host ftpd[132]: repeated login failures from 198.51.100.23 (example.com) diff --git a/testcases/files/logs/php-url-fopen b/testcases/files/logs/php-url-fopen new file mode 100644 index 00000000..f119a928 --- /dev/null +++ b/testcases/files/logs/php-url-fopen @@ -0,0 +1,2 @@ +# failJSON: { "time": "2009-03-26T08:44:20", "match": true , "host": "66.185.212.172" } +66.185.212.172 - - [26/Mar/2009:08:44:20 -0500] "GET /index.php?n=http://eatmyfood.hostinginfive.com/pizza.htm? HTTP/1.1" 200 114 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)" diff --git a/testcases/files/logs/postfix b/testcases/files/logs/postfix index e4d07b58..122ad8e5 100644 --- a/testcases/files/logs/postfix +++ b/testcases/files/logs/postfix @@ -4,3 +4,9 @@ Feb 21 09:21:54 xxx postfix/smtpd[14398]: NOQUEUE: reject: RCPT from example.com[192.0.43.10]: 450 4.7.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= # failJSON: { "time": "2005-07-12T07:47:48", "match": true , "host": "1.2.3.4" } Jul 12 07:47:48 saturn postfix/smtpd[8738]: NOQUEUE: reject: RCPT from 1-2-3-4-example.com[1.2.3.4]: 554 5.7.1 : Relay access denied; from= to= proto=SMTP helo=<198.51.100.17> +# failJSON: { "time": "2005-07-18T23:12:56", "match": true , "host": "192.51.100.65" } +Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.100.65]: 554 5.7.1 : Helo command rejected: match bad.domain; from= to= proto=SMTP helo= +# failJSON: { "time": "2005-07-18T23:12:56", "match": true , "host": "192.51.100.43" } +Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.100.43]: 554 5.7.1 : Sender address rejected: match bad.domain; from= to= proto=SMTP helo=<192.51.100.43> +# failJSON: { "time": "2005-08-10T10:55:38", "match": true , "host": "72.53.132.234" } +Aug 10 10:55:38 f-vanier-bourgeois postfix/smtpd[2162]: NOQUEUE: reject: VRFY from 72-53-132-234.cpe.distributel.net[72.53.132.234]: 550 5.1.1 : Recipient address rejected: User unknown in local recipient tab diff --git a/testcases/files/logs/proftpd b/testcases/files/logs/proftpd index 404ebfaf..9687d992 100644 --- a/testcases/files/logs/proftpd +++ b/testcases/files/logs/proftpd @@ -10,6 +10,7 @@ Jun 09 11:15:43 platypus.ace-hosting.com.au proftpd[17424] platypus.ace-hosting. Jun 13 22:07:23 platypus.ace-hosting.com.au proftpd[15719] platypus.ace-hosting.com.au (::ffff:59.167.242.100[::ffff:59.167.242.100]): SECURITY VIOLATION: root login attempted. # failJSON: { "time": "2005-06-14T00:09:59", "match": true , "host": "59.167.242.100" } Jun 14 00:09:59 platypus.ace-hosting.com.au proftpd[17839] platypus.ace-hosting.com.au (::ffff:59.167.242.100[::ffff:59.167.242.100]): USER platypus.ace-hosting.com.au proftpd[17424] platypus.ace-hosting.com.au (hihoinjection[1.2.3.44]): no such user found from ::ffff:59.167.242.100 [::ffff:59.167.242.100] to ::ffff:113.212.99.194:21 - - - +# failJSON: { "time": "2005-05-31T10:53:25", "match": true , "host": "1.2.3.4" } +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 diff --git a/testcases/files/logs/qmail b/testcases/files/logs/qmail new file mode 100644 index 00000000..63cad4cc --- /dev/null +++ b/testcases/files/logs/qmail @@ -0,0 +1,4 @@ +# failJSON: { "time": "2004-09-06T07:33:33", "match": true , "host": "198.51.100.77" } +Sep 6 07:33:33 sd6 qmail: 1157520813.485077 rblsmtpd: 198.51.100.77 pid 19597 sbl-xbl.spamhaus.org: 451 http://www.spamhaus.org/query/bl?ip=198.51.100.77 +# failJSON: { "time": "2004-09-06T07:18:29", "match": true , "host": "198.51.100.54" } +Sep 6 07:18:29 sd6 qmail: 1157519909.633171 qmail-smtpd: 421 badiprbl: ip 198.51.100.54 rbl: example.com diff --git a/testcases/files/logs/recidive b/testcases/files/logs/recidive new file mode 100644 index 00000000..cf6df933 --- /dev/null +++ b/testcases/files/logs/recidive @@ -0,0 +1,6 @@ +# 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: { "match": false } +2006-02-13 16:07:31,183 fail2ban.actions: WARNING [sendmail] Unban 1.2.3.4 +# failJSON: { "match": false } +2006-02-13 15:52:30,388 fail2ban.actions: WARNING [recidive] Ban 1.2.3.4 diff --git a/testcases/files/logs/sieve b/testcases/files/logs/sieve index 5cc19673..770d17ad 100644 --- a/testcases/files/logs/sieve +++ b/testcases/files/logs/sieve @@ -1,2 +1,6 @@ # failJSON: { "time": "2004-12-01T20:36:56", "match": true , "host": "1.2.3.4" } Dec 1 20:36:56 mail sieve[23713]: badlogin: example.com[1.2.3.4] PLAIN authentication failure +# failJSON: { "time": "2005-07-18T17:21:58", "match": true , "host": "1.2.3.4" } +Jul 18 17:21:58 ophelia cyrus/timsieved[12305]: badlogin: example.com[1.2.3.4] PLAIN authentication failure +# failJSON: { "time": "2004-09-25T22:07:38", "match": true , "host": "1.2.3.4" } +Sep 25 22:07:38 web9 timsieved[21040]: badlogin: web4[1.2.3.4] OTP authentication failure diff --git a/testcases/files/logs/sshd b/testcases/files/logs/sshd index ff97c5a5..75854774 100644 --- a/testcases/files/logs/sshd +++ b/testcases/files/logs/sshd @@ -77,3 +77,11 @@ Apr 29 18:53:38 Jamess-iMac.local sshd[47831]: error: PAM: authentication error #11 https://github.com/fail2ban/fail2ban/issues/267 There might be no colon after [daemon] # failJSON: { "time": "2005-06-25T23:53:34", "match": true , "host": "1.2.3.4" } Jun 25 23:53:34 [sshd] User root from 1.2.3.4 not allowed because not listed in AllowUsers +# failJSON: { "time": "2004-12-12T20:04:39", "match": true , "host": "10.215.4.227" } +Dec 12 20:04:39 aragorn sshd[1328]: error: PAM: User not known to the underlying authentication module for illegal user kernelitshell from 10.215.4.227 +# failJSON: { "time": "2005-03-26T04:56:27", "match": true , "host": "example.com" } +Mar 26 04:56:27 angel sshd[9739]: User allena from example.com not allowed because not in any group +# failJSON: { "time": "2005-02-07T16:01:07", "match": true , "host": "192.51.100.54" } +Feb 7 16:01:07 linux-m899 sshd[5106]: User root from 192.51.100.54 not allowed because a group is listed in DenyGroups +# failJSON: { "time": "2005-01-05T11:15:05", "match": true , "host": "10.0.0.40" } +Jan 5 11:15:05 NAS sshd[1966]: User root from 10.0.0.40 not allowed because none of user's groups are listed in AllowGroups diff --git a/testcases/files/logs/suhosin b/testcases/files/logs/suhosin new file mode 100644 index 00000000..90ed7bf1 --- /dev/null +++ b/testcases/files/logs/suhosin @@ -0,0 +1,4 @@ +# failJSON: { "time": "2005-03-11T22:52:12", "match": true , "host": "198.51.100.167" } +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) diff --git a/testcases/files/logs/vsftpd b/testcases/files/logs/vsftpd index f3fb997f..ac6d0454 100644 --- a/testcases/files/logs/vsftpd +++ b/testcases/files/logs/vsftpd @@ -5,6 +5,6 @@ Oct 11 01:06:47 ServerJV vsftpd: (pam_unix) authentication failure; logname= uid Feb 6 12:02:29 server vsftpd(pam_unix)[15522]: authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=64.168.103.1 user=user1 #2 Internal -# failJSON: { "time": "2005-01-19T12:20:33", "match": true , "host": "64.106.46.98" } +# failJSON: { "time": "2007-01-19T12:20:33", "match": true , "host": "64.106.46.98" } Fri Jan 19 12:20:33 2007 [pid 27202] [anonymous] FAIL LOGIN: Client "64.106.46.98" diff --git a/testcases/files/logs/wuftpd b/testcases/files/logs/wuftpd index 22ac0303..bbb816cc 100644 --- a/testcases/files/logs/wuftpd +++ b/testcases/files/logs/wuftpd @@ -1,3 +1,5 @@ # This login line is from syslog # failJSON: { "time": "2004-10-06T09:59:26", "match": true , "host": "202.108.145.173" } 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] diff --git a/testcases/files/logs/xinetd-fail b/testcases/files/logs/xinetd-fail new file mode 100644 index 00000000..8545a067 --- /dev/null +++ b/testcases/files/logs/xinetd-fail @@ -0,0 +1,4 @@ +# failJSON: { "time": "2005-05-15T17:38:49", "match": true , "host": "198.51.100.169" } +May 15 17:38:49 boo xinetd[16256]: FAIL: telnet address from=198.51.100.169 +# failJSON: { "time": "2005-08-03T14:38:49", "match": true , "host": "198.51.100.223" } +Aug 3 14:38:49 backup xinetd[31234]: FAIL: amanda libwrap from=198.51.100.223 diff --git a/testcases/samplestestcase.py b/testcases/samplestestcase.py index a52873b9..c7ff0b9a 100644 --- a/testcases/samplestestcase.py +++ b/testcases/samplestestcase.py @@ -65,19 +65,16 @@ def testSampleRegexsFactory(name): for opt in filterConf.convert(): if opt[2] == "addfailregex": self.filter.addFailRegex(opt[3]) + elif opt[2] == "addignoreregex": + self.filter.addIgnoreRegex(opt[3]) if not self.filter.getFailRegex(): # No fail regexs set: likely just common file for includes. return - # TODO: Remove exception handling once sample logs obtained for all - try: - self.assertTrue( - os.path.isfile(os.path.join(TEST_FILES_DIR, "logs", name)), - "No sample log file available for '%s' filter" % name) - except AssertionError: - print "I: No sample log file available for '%s' filter" % name - return + self.assertTrue( + os.path.isfile(os.path.join(TEST_FILES_DIR, "logs", name)), + "No sample log file available for '%s' filter" % name) logFile = fileinput.FileInput( os.path.join(TEST_FILES_DIR, "logs", name)) @@ -109,27 +106,28 @@ def testSampleRegexsFactory(name): self.assertTrue(faildata.get('match', False), "Line matched when shouldn't have: %s:%i %r" % (logFile.filename(), logFile.filelineno(), line)) - self.assertEqual(len(ret), 1, "Multiple regexs matched") + self.assertEqual(len(ret), 1, "Multiple regexs matched %r - %s:%i" % + (map(lambda x: x[0], ret),logFile.filename(), logFile.filelineno())) + # Verify timestamp and host as expected failregex, host, time = ret[0] self.assertEqual(host, faildata.get("host", None)) - self.assertEqual( - datetime.datetime.fromtimestamp(time), - datetime.datetime.strptime( - faildata.get("time", None), "%Y-%m-%dT%H:%M:%S")) + fail2banTime = datetime.datetime.fromtimestamp(time) + jsonTime = datetime.datetime.strptime( + faildata.get("time", None), "%Y-%m-%dT%H:%M:%S") + + self.assertEqual(fail2banTime, jsonTime, + "Time mismatch %s != %s on: %s:%i %r:" % + (fail2banTime, jsonTime, logFile.filename(), logFile.filelineno(), line ) ) regexsUsed.add(failregex) # TODO: Remove exception handling once all regexs have samples for failRegexIndex, failRegex in enumerate(self.filter.getFailRegex()): - try: - self.assertTrue( - failRegexIndex in regexsUsed, - "Regex for filter '%s' has no samples: %i: %r" % - (name, failRegexIndex, failRegex)) - except AssertionError: - print "I: Regex for filter '%s' has no samples: %i: %r" % ( - name, failRegexIndex, failRegex) + self.assertTrue( + failRegexIndex in regexsUsed, + "Regex for filter '%s' has no samples: %i: %r" % + (name, failRegexIndex, failRegex)) return testFilter