mirror of
https://github.com/fail2ban/fail2ban.git
synced 2026-10-21 19:42:11 +00:00
Merge branch 'fail2ban:master' into master
This commit is contained in:
commit
c6185116a0
40 changed files with 392 additions and 106 deletions
2
.github/workflows/codespell.yml
vendored
2
.github/workflows/codespell.yml
vendored
|
|
@ -17,6 +17,6 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v7
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
|
|
|
|||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
|
@ -22,15 +22,15 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-alpha.5', pypy3.11]
|
||||
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-rc.2', pypy3.11]
|
||||
fail-fast: false
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
|
|
|||
22
ChangeLog
22
ChangeLog
|
|
@ -7,7 +7,24 @@
|
|||
Fail2Ban: Changelog
|
||||
===================
|
||||
|
||||
ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
|
||||
ver. 1.1.2-dev-1 (20??/??/??) - development nightly edition
|
||||
-----------
|
||||
|
||||
### Fixes
|
||||
* `filter.d/nginx-botsearch.conf` - error-log regex extended with support of journal format (gh-3732)
|
||||
* `filter.d/apache-noscript.conf` - match a request for the `cgi-bin` directory itself (no script name after it),
|
||||
the `cgi-bin` alternative required a following path segment so `/cgi-bin/` and `/cgi-bin` were not matched (gh-4217)
|
||||
* sockets passed via `systemd` socket activation (`sd_listen_fds(3)`) are not shut down or removed anymore (gh-4225)
|
||||
* `paths-*.conf`: add default banactions for several includes depending on default net-filter for distribution (gh-4224, gh-4229):
|
||||
`paths-arch.conf`, `paths-fedora.conf`, `paths-opensuse.conf` - nftables, `paths-freebsd.conf`, `paths-osx.conf ` - pf;
|
||||
|
||||
### New Features and Enhancements
|
||||
* `filter.d/cowrie.conf` - new filter and jail for Cowrie SSH/Telnet honeypot JSON log output (gh-4216)
|
||||
* `filter.d/gitlab.conf` - `logtype` option `json` adding support for the `application_json.log` format that
|
||||
superseded the deprecated `application.log`; default type `file` keeps the previous behaviour (gh-3566, gh-4230)
|
||||
|
||||
|
||||
ver. 1.1.1 (2026/08/15) - triple-one-win
|
||||
-----------
|
||||
|
||||
### Compatibility
|
||||
|
|
@ -18,6 +35,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
|
|||
to use it as before set `mode = more` for exim jail, but be aware of the consequences.
|
||||
|
||||
### Fixes
|
||||
* fixes catastrophic backtracking explosion for REs in domino-smtp and dovecot filters (GHSA-33wh-ccjc-p397, gh-4221)
|
||||
* fixes `systemd` bug with missing journal descriptor after rotation by reopening of journal if it is recognized as not alive (gh-3929)
|
||||
* improve threaded clean-up of all filters, new thread functions `afterStop` (to force clean-up after stop) and `done`, invoking `afterStop` once
|
||||
* ensure journal-reader is always closed (additional prevention against leaks and "too many open files"), thereby avoid sporadic segfault
|
||||
|
|
@ -65,6 +83,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
|
|||
- add optional `NOQUEUE:` prefix to ddos regex (gh-4072)
|
||||
- internal parameter `_pref` is renamed to `_cmd`, `_pref` matches now optional prefix like `NOQUEUE: ` etc
|
||||
- modes `ddos` and `aggressive` extended to match `rate limit exceeded` for connection or message delivery request rates (gh-3265, gh-4073)
|
||||
- modes `aggressive` extended to match `hostname does not resolve to address` (gh-4218, gh-4078)
|
||||
* `filter.d/dropbear.conf`:
|
||||
- recognizes extra pid/timestamp if logged into stdout/journal, added `journalmatch` (gh-3597)
|
||||
- failregex extended to match different format of "Exit before auth" message (gh-3791)
|
||||
|
|
@ -83,6 +102,7 @@ ver. 1.1.1-dev-1 (20??/??/??) - development nightly edition
|
|||
* `filter.d/froxlor-auth.conf` - updated the regex to the new logging situation for froxlor and changed logpath in jail.conf (gh-4075).
|
||||
|
||||
### New Features and Enhancements
|
||||
* `fail2ban-server` supports `systemd` socket activation (`sd_listen_fds(3)`); optional `fail2ban.socket` unit provided
|
||||
* backend `systemd` extended with new parameter `rotated` (default `false`, as prevention against "too many open files"),
|
||||
that allows to monitor only actual journals and ignore now a lot of rotated files by default; so can drastically reduce
|
||||
amount of used file descriptors, normally to 1 or 2 descriptors per jail (gh-3391)
|
||||
|
|
|
|||
12
MANIFEST
12
MANIFEST
|
|
@ -11,6 +11,7 @@ config/action.d/bsd-ipfw.conf
|
|||
config/action.d/cloudflare.conf
|
||||
config/action.d/cloudflare-token.conf
|
||||
config/action.d/complain.conf
|
||||
config/action.d/csf.conf
|
||||
config/action.d/dshield.conf
|
||||
config/action.d/dummy.conf
|
||||
config/action.d/firewallcmd-allports.conf
|
||||
|
|
@ -129,6 +130,7 @@ config/filter.d/nginx-http-auth.conf
|
|||
config/filter.d/nginx-limit-req.conf
|
||||
config/filter.d/nsd.conf
|
||||
config/filter.d/openhab.conf
|
||||
config/filter.d/openvpn.conf
|
||||
config/filter.d/openwebmail.conf
|
||||
config/filter.d/oracleims.conf
|
||||
config/filter.d/pam-generic.conf
|
||||
|
|
@ -138,6 +140,7 @@ config/filter.d/php-url-fopen.conf
|
|||
config/filter.d/portsentry.conf
|
||||
config/filter.d/postfix.conf
|
||||
config/filter.d/proftpd.conf
|
||||
config/filter.d/proxmox.conf
|
||||
config/filter.d/pure-ftpd.conf
|
||||
config/filter.d/qmail.conf
|
||||
config/filter.d/recidive.conf
|
||||
|
|
@ -162,10 +165,12 @@ config/filter.d/suhosin.conf
|
|||
config/filter.d/tine20.conf
|
||||
config/filter.d/traefik-auth.conf
|
||||
config/filter.d/uwimap-auth.conf
|
||||
config/filter.d/vaultwarden.conf
|
||||
config/filter.d/vsftpd.conf
|
||||
config/filter.d/webmin-auth.conf
|
||||
config/filter.d/wuftpd.conf
|
||||
config/filter.d/xinetd-fail.conf
|
||||
config/filter.d/xrdp.conf
|
||||
config/filter.d/znc-adminlog.conf
|
||||
config/filter.d/zoneminder.conf
|
||||
config/jail.conf
|
||||
|
|
@ -340,6 +345,7 @@ fail2ban/tests/files/logs/nginx-http-auth
|
|||
fail2ban/tests/files/logs/nginx-limit-req
|
||||
fail2ban/tests/files/logs/nsd
|
||||
fail2ban/tests/files/logs/openhab
|
||||
fail2ban/tests/files/logs/openvpn
|
||||
fail2ban/tests/files/logs/openwebmail
|
||||
fail2ban/tests/files/logs/oracleims
|
||||
fail2ban/tests/files/logs/pam-generic
|
||||
|
|
@ -349,6 +355,7 @@ fail2ban/tests/files/logs/php-url-fopen
|
|||
fail2ban/tests/files/logs/portsentry
|
||||
fail2ban/tests/files/logs/postfix
|
||||
fail2ban/tests/files/logs/proftpd
|
||||
fail2ban/tests/files/logs/proxmox
|
||||
fail2ban/tests/files/logs/pure-ftpd
|
||||
fail2ban/tests/files/logs/qmail
|
||||
fail2ban/tests/files/logs/recidive
|
||||
|
|
@ -373,10 +380,12 @@ fail2ban/tests/files/logs/suhosin
|
|||
fail2ban/tests/files/logs/tine20
|
||||
fail2ban/tests/files/logs/traefik-auth
|
||||
fail2ban/tests/files/logs/uwimap-auth
|
||||
fail2ban/tests/files/logs/vaultwarden
|
||||
fail2ban/tests/files/logs/vsftpd
|
||||
fail2ban/tests/files/logs/webmin-auth
|
||||
fail2ban/tests/files/logs/wuftpd
|
||||
fail2ban/tests/files/logs/xinetd-fail
|
||||
fail2ban/tests/files/logs/xrdp
|
||||
fail2ban/tests/files/logs/znc-adminlog
|
||||
fail2ban/tests/files/logs/zoneminder
|
||||
fail2ban/tests/files/logs/zzz-generic-example
|
||||
|
|
@ -390,6 +399,7 @@ fail2ban/tests/files/testcase-journal.log
|
|||
fail2ban/tests/files/testcase-multiline.log
|
||||
fail2ban/tests/files/testcase-usedns.log
|
||||
fail2ban/tests/files/testcase-wrong-char.log
|
||||
fail2ban/tests/files/test-ign-ips-file
|
||||
fail2ban/tests/files/zzz-sshd-obsolete-multiline.log
|
||||
fail2ban/tests/filtertestcase.py
|
||||
fail2ban/tests/__init__.py
|
||||
|
|
@ -410,7 +420,7 @@ files/fail2ban-logrotate
|
|||
files/fail2ban-openrc.conf
|
||||
files/fail2ban-openrc.init.in
|
||||
files/fail2ban.service.in
|
||||
files/fail2ban-tmpfiles.conf
|
||||
files/fail2ban.socket.in
|
||||
files/fail2ban.upstart
|
||||
files/gen_badbots
|
||||
files/ipmasq-ZZZzzz_fail2ban.rul
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
/ _|__ _(_) |_ ) |__ __ _ _ _
|
||||
| _/ _` | | |/ /| '_ \/ _` | ' \
|
||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||
v1.1.0.dev1 20??/??/??
|
||||
v1.1.2.dev1 20??/??/??
|
||||
|
||||
## Fail2Ban: ban hosts that cause multiple authentication errors
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ type = multiport
|
|||
rule_match-custom =
|
||||
rule_match-allports = meta l4proto \{ <protocol> \}
|
||||
rule_match-multiport = $proto dport \{ $(echo '<port>' | sed s/:/-/g) \}
|
||||
rule_match-allprotos-custom =
|
||||
rule_match-allprotos-allports =
|
||||
rule_match-allprotos-multiport = th dport \{ $(echo '<port>' | sed s/:/-/g) \}
|
||||
match = <rule_match-<type>>
|
||||
match_allprotos = <rule_match-allprotos-<type>>
|
||||
|
||||
# Option: rule_stat
|
||||
# Notes.: statement for nftables filter rule.
|
||||
|
|
@ -43,6 +47,7 @@ match = <rule_match-<type>>
|
|||
# Values: nftables statement
|
||||
#
|
||||
rule_stat = %(match)s <addr_family> saddr @<addr_set> <blocktype>
|
||||
rule_stat_allprotos = %(match_allprotos)s <addr_family> saddr @<addr_set> <blocktype>
|
||||
|
||||
# optional iterator over protocol's:
|
||||
_nft_for_proto-custom-iter =
|
||||
|
|
@ -56,9 +61,13 @@ _nft_list = <nftables> -a list chain <table_family> <table> <chain>
|
|||
_nft_get_handle_id = sed -nE 's/.*@<addr_set>\s+.*\s+\#\s*(handle\s+[0-9]+)$/\1/p'
|
||||
|
||||
_nft_add_set = <nftables> add set <table_family> <table> <addr_set> \{ type <addr_type>\;<addr_options> \}
|
||||
if [ '<protocol>' != "all" ]; then
|
||||
<_nft_for_proto-<type>-iter>
|
||||
<nftables> add rule <table_family> <table> <chain> %(rule_stat)s
|
||||
<_nft_for_proto-<type>-done>
|
||||
else
|
||||
<nftables> add rule <table_family> <table> <chain> %(rule_stat_allprotos)s
|
||||
fi
|
||||
_nft_del_set = { %(_nft_list)s | %(_nft_get_handle_id)s; } | while read -r hdl; do
|
||||
<nftables> delete rule <table_family> <table> <chain> $hdl; done
|
||||
<nftables> delete set <table_family> <table> <addr_set>
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ before = apache-common.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl|\bcgi-bin/)
|
||||
script = /\S*(?:php(?:[45]|[.-]cgi)?|\.asp|\.exe|\.pl|\bcgi-bin)
|
||||
|
||||
prefregex = ^%(_apache_error_client)s (?:AH0(?:01(?:28|30)|1(?:264|071)|2811): )?(?=(?:[Ff]ile|[Ss]cript|[Gg]ot error|stderr from) )<F-CONTENT>.+</F-CONTENT>$
|
||||
|
||||
failregex = ^(?:(?:[Ff]ile does not exist|[Ss]cript not found or unable to stat): <script>\b|[Gg]ot error '[Pp]rimary script unknown\b)
|
||||
^(?:stderr from |script (?P<_q>'))<script>\S*(?(_q)'|) (?:script )?(?:does not exist|not found or unable to stat)
|
||||
^(?:stderr from |script (?P<_q>'))<script>\b\S*(?(_q)'|) (?:script )?(?:does not exist|not found or unable to stat)
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ datepattern = %(lt_short/datepattern)s
|
|||
|
||||
[lt_rfc5424]
|
||||
# RFC 5424 log-format, see gh-2309:
|
||||
#__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ \S+\s+
|
||||
__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ (?:[^\[\]\s]+|(?:\[(?:[^\]"]*|"[^"]*")*\])+)\s+
|
||||
__prefix_line = \s*<__hostname> <__daemon_re> \d+ \S+ (?:-|(?:\[(?:[^\]"]|"(?:[^"\\]|\\.)*")*\])+)\s+
|
||||
datepattern = ^<\d+>\d+\s+{DATE}
|
||||
|
||||
# Author: Yaroslav Halchenko, Sergey G. Brester (aka sebres)
|
||||
|
|
|
|||
22
config/filter.d/cowrie.conf
Normal file
22
config/filter.d/cowrie.conf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Fail2Ban filter for Cowrie SSH/Telnet honeypot (https://github.com/cowrie/cowrie)
|
||||
# Detects new connections logged in Cowrie's JSON output format
|
||||
# (log.format = json / output.jsonlog in cowrie.cfg), one JSON object per line, e.g.:
|
||||
# {"session":"2a5e755afa79","protocol":"ssh","src_ip":"1.2.3.4","src_port":41000,
|
||||
# "dst_ip":"172.18.0.2","dst_port":2222,"eventid":"cowrie.session.connect",
|
||||
# "sensor":"myhost","uuid":"8c5b1af2-...","timestamp":"2024-01-01T00:00:00.000000Z",
|
||||
# "message":"New connection: ..."}
|
||||
# As Cowrie is a honeypot, any connection to it is unsolicited by definition.
|
||||
|
||||
[Init]
|
||||
|
||||
datepattern = "timestamp":"(%%ExY-%%m-%%dT%%H:%%M:%%S(?:\.%%f)?Z?)"
|
||||
|
||||
[Definition]
|
||||
|
||||
# bounded run of arbitrary "key":value pairs (string/number), used to skip
|
||||
# over fields we don't care about without resorting to an unbounded ".*"
|
||||
_kv = (?:\s*"(?!(?:src_ip|eventid)\b)\w[^"]+"\s*:\s*(?:"[^"]+"|\w+)\s*[,\}]){0,20}
|
||||
datepattern = ^\{%(_kv)s("timestamp":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\s*,\s*)
|
||||
failregex = ^\{%(_kv)s"src_ip":"<ADDR>",%(_kv)s"eventid":"cowrie\.session\.connect"
|
||||
|
||||
ignoreregex =
|
||||
|
|
@ -23,10 +23,7 @@
|
|||
|
||||
[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 "<HOST>" can
|
||||
# be used for standard IP/hostname matching and is only an alias for
|
||||
# (?:::f{4,6}:)?(?P<host>\S+)
|
||||
# Notes.: regex to match the password failure messages in the logfile.
|
||||
# Values: TEXT
|
||||
#
|
||||
# Sample log entries (used different time formats and an extra sample with process info in front of date)
|
||||
|
|
@ -37,9 +34,9 @@
|
|||
|
||||
__prefix = (?:\[[^\]]+\])?\s*
|
||||
__opt_data = (?::|\s+\[[^\]]+\])
|
||||
failregex = ^%(__prefix)sSMTP Server%(__opt_data)s Authentication failed for user .*? \; connecting host \[?<HOST>\]?$
|
||||
^%(__prefix)ssmtp: (?:[^\[]+ )*\[?<HOST>\]? authentication failure using internet password\s*$
|
||||
^%(__prefix)sSMTP Server%(__opt_data)s Connection from \[?<HOST>\]? rejected for policy reasons\.
|
||||
failregex = ^%(__prefix)sSMTP Server%(__opt_data)s Authentication failed for user .*? \; connecting host \[?<ADDR>\]?$
|
||||
^%(__prefix)ssmtp: (?:<F-USER>(?:[^\[ ]+ )*[^\[ ]+</F-USER> )?\[?<ADDR>\]? authentication failure using internet password\s*$
|
||||
^%(__prefix)sSMTP Server%(__opt_data)s Connection from \[?<ADDR>\]? rejected for policy reasons\.
|
||||
|
||||
# Option: ignoreregex
|
||||
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ _auth_driver = [a-z\-]+
|
|||
|
||||
_auth_worker = (?:dovecot: )?auth(?:-worker)?
|
||||
_auth_worker_info = (?:%(_auth_driver)s: )?(?:conn \w+:auth(?:-worker)? \([^\)]+\): auth(?:-worker)?<\d+>: )?
|
||||
_bypass_reject_reason = (?:: (?:\w+\([^\):]*\) \w+|[^\(]+))*
|
||||
_bypass_reject_reason = (?::(?: [^\( ]*|\([^\(]*\))*)?
|
||||
|
||||
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\((?:\S*,<ADDR>(?:,\S*)?|[^\)]+)\))?(?:<[^>]+>)?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:request \[\d+\]: )?(?:Info: )?%(_auth_worker_info)s<F-CONTENT>.+</F-CONTENT>$
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,32 @@
|
|||
# Fail2Ban filter for Gitlab
|
||||
# Detecting unauthorized access to the Gitlab Web portal
|
||||
# typically logged in /var/log/gitlab/gitlab-rails/application.log
|
||||
#
|
||||
# logtype "file" (default) reads the legacy application.log, typically logged in
|
||||
# /var/log/gitlab/gitlab-rails/application.log
|
||||
#
|
||||
# logtype "json" reads the structured application_json.log that superseded it
|
||||
# (application.log was deprecated by Gitlab in 15.10), typically logged in
|
||||
# /var/log/gitlab/gitlab-rails/application_json.log
|
||||
|
||||
[Definition]
|
||||
|
||||
# Log format: "file" (application.log) or "json" (application_json.log)
|
||||
logtype = file
|
||||
|
||||
failregex = <lt_<logtype>/failregex>
|
||||
datepattern = <lt_<logtype>/datepattern>
|
||||
ignoreregex =
|
||||
|
||||
[lt_file]
|
||||
|
||||
failregex = ^: Failed Login: username=<F-USER>.+</F-USER> ip=<HOST>$
|
||||
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
|
||||
|
||||
[lt_json]
|
||||
|
||||
# bounded run of "key":value pairs, used to skip over fields we don't care
|
||||
# about (order of tags in the message is significant, so relative positions
|
||||
# of time, meta.remote_ip and message are anchored below)
|
||||
_groupre = (?:\s*"(?!(?:time|meta\.remote_ip|message)\b)\w[^"]+"\s*:\s*(?:"[^"]+"|\w+)\s*[,\}])
|
||||
failregex = ^%(_groupre)s*"meta\.remote_ip":"<ADDR>"\s*,\s*%(_groupre)s*"message":"Failed Login: username=<F-USER>(?:\S*|[^"]*?)(?= ip=)</F-USER>
|
||||
datepattern = ^\{%(_groupre)s*"time":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\s*,\s*
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
|
||||
# Load regexes for filtering
|
||||
before = botsearch-common.conf
|
||||
nginx-error-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/<block> \S+\" 404 .+$
|
||||
^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
|
||||
^%(__prefix_line)s(\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ mdad-extra =
|
|||
mdpr-aggressive = (?:%(mdpr-auth)s|%(mdpr-normal)s|%(mdpr-ddos)s)
|
||||
mdre-aggressive = %(mdre-auth2)s
|
||||
%(mdre-normal)s
|
||||
^hostname \S+ does not resolve to address <ADDR>
|
||||
mdad-aggressive = %(mdad-ddos)s
|
||||
|
||||
mdpr-errors = too many errors after \S+
|
||||
|
|
|
|||
|
|
@ -865,6 +865,7 @@ protocol = udp
|
|||
logpath = /usr/local/vpnserver/security_log/*/sec.log
|
||||
|
||||
[gitlab]
|
||||
# for the newer application_json.log set: filter = gitlab[mode=json]
|
||||
port = http,https
|
||||
logpath = /var/log/gitlab/gitlab-rails/application.log
|
||||
|
||||
|
|
@ -995,3 +996,7 @@ logpath = /var/log/vaultwarden.log
|
|||
[xrdp]
|
||||
port = 3389
|
||||
logpath = /var/log/xrdp-sesman.log
|
||||
|
||||
[cowrie]
|
||||
port = ssh,telnet,2222,2223
|
||||
logpath = /var/log/cowrie/cowrie.json
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ after = paths-overrides.local
|
|||
|
||||
[DEFAULT]
|
||||
|
||||
banaction = nftables
|
||||
banaction_allports = nftables[type=allports]
|
||||
|
||||
apache_error_log = /var/log/httpd/*error_log
|
||||
|
||||
apache_access_log = /var/log/httpd/*access_log
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ after = paths-overrides.local
|
|||
|
||||
[DEFAULT]
|
||||
|
||||
banaction = nftables
|
||||
banaction_allports = nftables[type=allports]
|
||||
|
||||
syslog_mail = /var/log/maillog
|
||||
|
||||
syslog_mail_warn = /var/log/maillog
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ after = paths-overrides.local
|
|||
|
||||
[DEFAULT]
|
||||
|
||||
banaction = pf
|
||||
banaction_allports = pf[actiontype=<allports>]
|
||||
|
||||
# http://www.freebsd.org/doc/handbook/configtuning-syslog.html
|
||||
#
|
||||
syslog_mail = /var/log/maillog
|
||||
|
|
|
|||
|
|
@ -9,12 +9,27 @@ after = paths-overrides.local
|
|||
|
||||
[DEFAULT]
|
||||
|
||||
banaction = nftables
|
||||
banaction_allports = nftables[type=allports]
|
||||
|
||||
syslog_local0 = /var/log/messages
|
||||
|
||||
syslog_mail = /var/log/mail
|
||||
|
||||
syslog_mail_warn = %(syslog_mail)s
|
||||
|
||||
syslog_authpriv = %(syslog_local0)s
|
||||
|
||||
syslog_user = %(syslog_local0)s
|
||||
|
||||
syslog_ftp = %(syslog_local0)s
|
||||
|
||||
syslog_daemon = %(syslog_local0)s
|
||||
|
||||
apache_error_log = /var/log/apache2/*error_log
|
||||
|
||||
apache_access_log = /var/log/apache2/*access_log
|
||||
|
||||
pureftpd_log = %(syslog_local0)s
|
||||
|
||||
exim_main_log = /var/log/exim/main.log
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ after = paths-overrides.local
|
|||
|
||||
[DEFAULT]
|
||||
|
||||
banaction = pf
|
||||
banaction_allports = pf[actiontype=<allports>]
|
||||
|
||||
syslog_mail = /var/log/mail.log
|
||||
|
||||
syslog_mail_warn = /var/log/mail.warn
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ class AsyncServer(asyncore.dispatcher):
|
|||
asyncore.dispatcher.__init__(self)
|
||||
self.__transmitter = transmitter
|
||||
self.__sock = "/var/run/fail2ban/fail2ban.sock"
|
||||
self.__shutdown_socket = True
|
||||
self.__init = False
|
||||
self.__active = False
|
||||
self.__errCount = {'accept': 0, 'listen': 0}
|
||||
|
|
@ -236,35 +237,56 @@ class AsyncServer(asyncore.dispatcher):
|
|||
# Creates an instance of the handler class to handle the
|
||||
# request/response on the incoming connection.
|
||||
RequestHandler(conn, self.__transmitter)
|
||||
|
||||
|
||||
def setup_socket(self, sock, force):
|
||||
nfds = os.environ.get("LISTEN_FDS")
|
||||
if nfds and os.environ.get("LISTEN_PID") == str(os.getpid()):
|
||||
# systemd socket activation (sd_listen_fds(3)): reuse an inherited,
|
||||
# already bound and listening socket instead of creating our own.
|
||||
names = os.environ.get("LISTEN_FDNAMES", "").split(":")
|
||||
idx = names.index("fail2ban") if "fail2ban" in names else 0
|
||||
idx = min(idx, int(nfds) - 1)
|
||||
os.environ.pop("LISTEN_PID", None)
|
||||
os.environ.pop("LISTEN_FDS", None)
|
||||
os.environ.pop("LISTEN_FDNAMES", None)
|
||||
logSys.info("Using socket activation, inherited socket %r", sock)
|
||||
# prevent socket from being shut down on close
|
||||
self.__shutdown_socket = False
|
||||
self.set_socket(socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, fileno=3 + idx))
|
||||
self.socket.setblocking(False)
|
||||
self.accepting = True
|
||||
else:
|
||||
# Remove socket
|
||||
if os.path.exists(sock):
|
||||
logSys.error("Fail2ban seems to be already running")
|
||||
if force:
|
||||
logSys.warning("Forcing execution of the server")
|
||||
self._remove_sock()
|
||||
else:
|
||||
raise AsyncServerException("Server already running")
|
||||
# Creates the socket.
|
||||
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.set_reuse_addr()
|
||||
try:
|
||||
self.bind(sock)
|
||||
except Exception: # pragma: no cover
|
||||
raise AsyncServerException("Unable to bind socket %s" % self.__sock)
|
||||
self.listen(1)
|
||||
# Sets the init flag.
|
||||
self.__init = True
|
||||
AsyncServer.__markCloseOnExec(self.socket)
|
||||
|
||||
##
|
||||
# Starts the communication server.
|
||||
#
|
||||
# @param sock: socket file.
|
||||
# @param force: remove the socket file if exists.
|
||||
|
||||
|
||||
def start(self, sock, force, timeout=None, use_poll=False):
|
||||
self.__worker = threading.current_thread()
|
||||
self.__sock = sock
|
||||
# Remove socket
|
||||
if os.path.exists(sock):
|
||||
logSys.error("Fail2ban seems to be already running")
|
||||
if force:
|
||||
logSys.warning("Forcing execution of the server")
|
||||
self._remove_sock()
|
||||
else:
|
||||
raise AsyncServerException("Server already running")
|
||||
# Creates the socket.
|
||||
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.set_reuse_addr()
|
||||
try:
|
||||
self.bind(sock)
|
||||
except Exception: # pragma: no cover
|
||||
raise AsyncServerException("Unable to bind socket %s" % self.__sock)
|
||||
AsyncServer.__markCloseOnExec(self.socket)
|
||||
self.listen(1)
|
||||
# Sets the init flag.
|
||||
self.__init = self.__loop = self.__active = True
|
||||
self.setup_socket(sock, force)
|
||||
self.__loop = self.__active = True
|
||||
# Execute on start event (server ready):
|
||||
if self.onstart:
|
||||
self.onstart()
|
||||
|
|
@ -279,7 +301,7 @@ class AsyncServer(asyncore.dispatcher):
|
|||
if self.__active:
|
||||
self.__loop = False
|
||||
# shutdown socket here:
|
||||
if self.socket:
|
||||
if self.__shutdown_socket and self.socket:
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except socket.error: # pragma: no cover - normally unreachable
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
|
|||
__license__ = "GPL"
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
from glob import glob
|
||||
|
|
@ -60,11 +61,11 @@ def _globJournalFiles(flags=None, path=None):
|
|||
"""add journal files to set corresponding path and flags (without rotated *@*.journal)"""
|
||||
# system journal:
|
||||
if (flags is None) or (flags & journal.SYSTEM_ONLY):
|
||||
filesSet |= set(glob(_join(p,'system.journal'))) - set(glob(_join(p,'system*@*.journal')))
|
||||
filesSet |= set(glob(_join(p,'system.journal')))
|
||||
# current user-journal:
|
||||
if (flags is not None) and (flags & journal.CURRENT_USER):
|
||||
uid = os.geteuid()
|
||||
filesSet |= set(glob(_join(p,('user-%s.journal' % uid)))) - set(glob(_join(p,('user-%s@*.journal' % uid))))
|
||||
filesSet |= set(glob(_join(p,('user-%s.journal' % uid))))
|
||||
# all local journals:
|
||||
if (flags is None) or not (flags & (journal.SYSTEM_ONLY|journal.CURRENT_USER)):
|
||||
filesSet |= set(glob(_join(p,'*.journal'))) - set(glob(_join(p,'*@*.journal')))
|
||||
|
|
@ -102,6 +103,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
JournalFilter.__init__(self, jail, **kwargs)
|
||||
self.__modified = 0
|
||||
# Initialise systemd-journal connection
|
||||
self.__lock = threading.Lock()
|
||||
self.__journal = journal.Reader(**self.__jrnlargs)
|
||||
self.__matches = []
|
||||
self.__bypassInvalidateMsg = 0
|
||||
|
|
@ -170,7 +172,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
"""
|
||||
try:
|
||||
# open?
|
||||
if self.__journal.closed: # pragma: no cover
|
||||
if not self.__journal or self.__journal.closed: # pragma: no cover
|
||||
return False
|
||||
# has cursor? if it is broken (e. g. no descriptor) - it'd raise this:
|
||||
# OSError: [Errno 99] Cannot assign requested address
|
||||
|
|
@ -196,10 +198,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
ja.get('flags', 0), ja.get('path'), ja.get('files'), ja.get('namespace'))
|
||||
except:
|
||||
# cannot reopen in that way, so simply recreate reader:
|
||||
self.closeJournal()
|
||||
self._closeJournal()
|
||||
self.__journal = journal.Reader(**self.__jrnlargs)
|
||||
# restore journalmatch specified for the jail:
|
||||
self.resetJournalMatches()
|
||||
self._resetJournalMatches()
|
||||
# just to avoid "Invalidate signaled" happening again after reopen:
|
||||
self.__bypassInvalidateMsg = MyTime.time() + 1
|
||||
|
||||
|
|
@ -226,28 +228,32 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
# @param match journalctl syntax matches in list structure
|
||||
|
||||
def addJournalMatch(self, match):
|
||||
newMatches = [[]]
|
||||
for match_element in match:
|
||||
if match_element == "+":
|
||||
newMatches.append([])
|
||||
with self.__lock:
|
||||
if not self.__journal or self.__journal.closed:
|
||||
return
|
||||
newMatches = [[]]
|
||||
for match_element in match:
|
||||
if match_element == "+":
|
||||
newMatches.append([])
|
||||
else:
|
||||
newMatches[-1].append(match_element)
|
||||
try:
|
||||
self._addJournalMatches(newMatches)
|
||||
except ValueError:
|
||||
logSys.error(
|
||||
"Error adding journal match for: %r", " ".join(match))
|
||||
self._resetJournalMatches()
|
||||
raise
|
||||
else:
|
||||
newMatches[-1].append(match_element)
|
||||
try:
|
||||
self._addJournalMatches(newMatches)
|
||||
except ValueError:
|
||||
logSys.error(
|
||||
"Error adding journal match for: %r", " ".join(match))
|
||||
self.resetJournalMatches()
|
||||
raise
|
||||
else:
|
||||
logSys.info("[%s] Added journal match for: %r", self.jailName,
|
||||
" ".join(match))
|
||||
logSys.info("[%s] Added journal match for: %r", self.jailName,
|
||||
" ".join(match))
|
||||
|
||||
##
|
||||
# Reset a journal match filter called on removal or failure
|
||||
#
|
||||
# @return None
|
||||
|
||||
def resetJournalMatches(self):
|
||||
def _resetJournalMatches(self):
|
||||
self.__journal.flush_matches()
|
||||
logSys.debug("[%s] Flushed all journal matches", self.jailName)
|
||||
match_copy = self.__matches[:]
|
||||
|
|
@ -266,19 +272,22 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
# @param match journalctl syntax matches
|
||||
|
||||
def delJournalMatch(self, match=None):
|
||||
# clear all:
|
||||
if match is None:
|
||||
if not self.__matches:
|
||||
with self.__lock:
|
||||
if not self.__journal or self.__journal.closed:
|
||||
return
|
||||
del self.__matches[:]
|
||||
# delete by index:
|
||||
elif match in self.__matches:
|
||||
del self.__matches[self.__matches.index(match)]
|
||||
else:
|
||||
raise ValueError("Match %r not found" % match)
|
||||
self.resetJournalMatches()
|
||||
logSys.info("[%s] Removed journal match for: %r", self.jailName,
|
||||
match if match else '*')
|
||||
# clear all:
|
||||
if match is None:
|
||||
if not self.__matches:
|
||||
return
|
||||
del self.__matches[:]
|
||||
# delete by index:
|
||||
elif match in self.__matches:
|
||||
del self.__matches[self.__matches.index(match)]
|
||||
else:
|
||||
raise ValueError("Match %r not found" % match)
|
||||
self._resetJournalMatches()
|
||||
logSys.info("[%s] Removed journal match for: %r", self.jailName,
|
||||
match if match else '*')
|
||||
|
||||
##
|
||||
# Get current journal match filter
|
||||
|
|
@ -457,10 +466,11 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
except OSError:
|
||||
pass
|
||||
# if it is not alive - reopen:
|
||||
if not self._journalAlive:
|
||||
logSys.log(logging.MSG, "[%s] Journal reader seems to be offline, reopen journal", self.jailName)
|
||||
self._reopenJournal()
|
||||
wcode = journal.NOP
|
||||
with self.__lock:
|
||||
if not self._journalAlive:
|
||||
logSys.log(logging.MSG, "[%s] Journal reader seems to be offline, reopen journal", self.jailName)
|
||||
self._reopenJournal()
|
||||
wcode = journal.NOP
|
||||
self.__modified = 0
|
||||
while self.active:
|
||||
logentry = None
|
||||
|
|
@ -527,7 +537,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
logSys.debug("[%s] filter exited (systemd)", self.jailName)
|
||||
return True
|
||||
|
||||
def closeJournal(self):
|
||||
def _closeJournal(self):
|
||||
try:
|
||||
jnl, self.__journal = self.__journal, None
|
||||
if jnl:
|
||||
|
|
@ -558,8 +568,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
|||
|
||||
def afterStop(self):
|
||||
"""Cleanup"""
|
||||
# close journal:
|
||||
self.closeJournal()
|
||||
with self.__lock:
|
||||
# close journal:
|
||||
self._closeJournal()
|
||||
# ensure positions of pending logs are up-to-date:
|
||||
if self._pendDBUpdates and self.jail.database:
|
||||
self._updateDBPending()
|
||||
|
|
|
|||
|
|
@ -26,3 +26,6 @@
|
|||
|
||||
# failJSON: { "time": "2024-12-18T23:58:03", "match": true , "host": "192.0.2.74", "desc": "script not found, changed log-format with stderr from (gh-3900)" }
|
||||
[Wed Dec 18 23:58:03.148113 2024] [cgi:error] [pid 16720:tid 1878] [client 192.0.2.74:35092] AH02811: stderr from /usr/lib/cgi-bin/luci: script not found or unable to stat
|
||||
|
||||
# failJSON: { "time": "2025-07-30T16:43:26", "match": true , "host": "192.0.2.5", "desc": "stderr from a cgi-bin directory, no script name and no trailing slash (gh-4040)" }
|
||||
[Wed Jul 30 16:43:26.968908 2025] [cgi:error] [pid 147397:tid 147442] [client 192.0.2.5:33570] AH02811: stderr from /srv/http/cgi-bin: script not found or unable to stat
|
||||
|
|
|
|||
28
fail2ban/tests/files/logs/cowrie
Normal file
28
fail2ban/tests/files/logs/cowrie
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Cowrie honeypot JSON log (log.format = json), real-world samples
|
||||
|
||||
# failJSON: { "time": "2026-08-13T00:00:34", "match": true, "host": "137.184.154.9" }
|
||||
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"eventid":"cowrie.session.connect","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.803989Z","message":"New connection: 137.184.154.9:37416 (172.18.0.2:2222) [session: 2a5e755afa79]"}
|
||||
|
||||
# not a connect event (client version) -> should not match
|
||||
# failJSON: { "match": false }
|
||||
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"version":"SSH-2.0-Go","eventid":"cowrie.client.version","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.808236Z","message":"Remote SSH version: SSH-2.0-Go"}
|
||||
|
||||
# not a connect event (kex, has arrays/nested values) -> should not match
|
||||
# failJSON: { "match": false }
|
||||
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"hassh":"16443846184eafde36765c9bab2f4397","hasshAlgorithms":"mlkem768x25519-sha256,curve25519-sha256","kexAlgs":["mlkem768x25519-sha256","curve25519-sha256"],"keyAlgs":["rsa-sha2-256-cert-v01@openssh.com","ssh-ed25519"],"encCS":["aes128-gcm@openssh.com","aes256-gcm@openssh.com"],"macCS":["hmac-sha2-256-etm@openssh.com"],"compCS":["none"],"langCS":[""],"eventid":"cowrie.client.kex","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:34.924330Z","message":"SSH client hassh fingerprint: 16443846184eafde36765c9bab2f4397"}
|
||||
|
||||
# not a connect event (login success) -> should not match
|
||||
# failJSON: { "match": false }
|
||||
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"username":"brun","password":"brun","eventid":"cowrie.login.success","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:35.273750Z","message":"login attempt [brun/brun] succeeded"}
|
||||
|
||||
# not a connect event (log closed) -> should not match
|
||||
# failJSON: { "match": false }
|
||||
{"session":"2a5e755afa79","protocol":"ssh","src_ip":"137.184.154.9","src_port":37416,"dst_ip":"172.18.0.2","dst_port":2222,"ttylog":"var/lib/cowrie/tty/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":30,"shasum":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","duplicate":true,"duration_ms":5,"eventid":"cowrie.log.closed","sensor":"580d0186ebeb","uuid":"8c5b1af2-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:00:35.513067Z","message":"Closing TTY Log: var/lib/cowrie/tty/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 after 5 milliseconds"}
|
||||
|
||||
# a second, different connect event -> should match
|
||||
# failJSON: { "time": "2026-08-13T00:05:00", "match": true, "host": "203.0.113.9" }
|
||||
{"session":"9f8e7d6c5b4a","protocol":"telnet","src_ip":"203.0.113.9","src_port":52344,"dst_ip":"172.18.0.2","dst_port":23,"eventid":"cowrie.session.connect","sensor":"580d0186ebeb","uuid":"9f8e7d6c-8fc4-11f1-9350-ee70fe9b2de3","timestamp":"2026-08-13T00:05:00.000000Z","message":"New connection: 203.0.113.9:52344 (172.18.0.2:23) [session: 9f8e7d6c5b4a]"}
|
||||
|
||||
# malformed / non-JSON line -> should not match
|
||||
# failJSON: { "match": false }
|
||||
not a json line at all
|
||||
|
|
@ -7,6 +7,14 @@
|
|||
# failJSON: { "time": "2016-11-07T22:21:20", "match": true , "host": "1.2.3.4" }
|
||||
2016-11-07 22:21:20 smtp: postmaster [1.2.3.4] authentication failure using internet password
|
||||
|
||||
# failJSON: { "time": "2016-11-07T22:21:21", "match": true , "host": "192.0.2.4", "user": "user with space in name" }
|
||||
2016-11-07 22:21:21 smtp: user with space in name [192.0.2.4] authentication failure using internet password
|
||||
|
||||
# failJSON: { "time": "2016-11-07T22:21:22", "match": true , "host": "192.0.2.5", "desc": "GHSA-33wh-ccjc-p397, positive case (match, no hangs)" }
|
||||
2016-11-07 22:21:22 smtp: A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 192.0.2.5 authentication failure using internet password
|
||||
# failJSON: { "match": false, "desc": "GHSA-33wh-ccjc-p397, negative case (bypass, no hangs)" }
|
||||
2016-11-07 22:21:23 smtp: A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A 192.0.2.5
|
||||
|
||||
# failJSON: { "time": "2018-09-19T17:25:50", "match": true , "host": "192.0.2.1", "desc":"different log-format" }
|
||||
2018-09-19 17:25:50 SMTP Server [0D14:0027-1334] Authentication failed for user Bad Hacker ; connecting host [192.0.2.1]
|
||||
# failJSON: { "time": "2018-09-19T17:25:52", "match": true , "host": "192.0.2.2", "desc":"gh-2228, rejected for policy reasons" }
|
||||
|
|
|
|||
|
|
@ -144,6 +144,11 @@ Aug 30 00:10:57 auth(info@example.com,192.0.2.110,sasl:login)<CvN+E95Uf4e7eEnj>:
|
|||
# failJSON: { "time": "2004-08-30T00:11:05", "match": true , "host": "192.0.2.110", "desc": "closed after 3 attempts (password mismatch), gh-4196" }
|
||||
Aug 30 00:11:05 imap-login: Info: Login aborted: Connection closed (auth failed, 3 attempts in 16 secs) (auth_failed): user=<info@example.com>, method=PLAIN, rip=192.0.2.110, lip=0.0.0.0, TLS, session=<CvN+E95Uf4e7eEnj>
|
||||
|
||||
# failJSON: { "time": "2004-08-31T16:06:05", "match": true , "host": "192.0.2.111", "desc": "GHSA-33wh-ccjc-p397, positive case (match, no hangs)" }
|
||||
Aug 31 16:06:05 s166-62-100-187 dovecot: imap-login: Disconnected: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: (reason: some unknown reason) (tried to use disallowed test auth): user=<>, rip=192.0.2.111
|
||||
# failJSON: { "match": false , "desc": "GHSA-33wh-ccjc-p397, negative case (bypass, no hangs)" }
|
||||
Aug 31 16:06:06 s166-62-100-187 dovecot: imap-login: Disconnected: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: a: test no match
|
||||
|
||||
# ---------------------------------------
|
||||
# Test-cases of aggressive mode:
|
||||
# ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -3,3 +3,14 @@
|
|||
2020-04-09T14:04:00.667Z: Failed Login: username=admin ip=80.10.11.12
|
||||
# failJSON: { "time": "2020-04-09T16:15:09", "match": true , "host": "80.10.11.12" }
|
||||
2020-04-09T14:15:09.344Z: Failed Login: username=user name ip=80.10.11.12
|
||||
|
||||
# Access of unauthorized host in /var/log/gitlab/gitlab-rails/application_json.log
|
||||
# filterOptions: [{"logtype": "json"}]
|
||||
|
||||
# failJSON: { "time": "2023-08-25T13:25:19", "match": true , "host": "87.250.224.11" }
|
||||
{"severity":"INFO","time":"2023-08-25T11:25:19.979Z","correlation_id":"01H8P721YTW1YH5Q7YCSJPVJ5T","meta.caller_id":"SessionsController#new","meta.remote_ip":"87.250.224.11","meta.feature_category":"system_access","meta.client_id":"ip/87.250.224.11","message":"Failed Login: username=admin ip=87.250.224.11"}
|
||||
# failJSON: { "time": "2023-08-25T13:31:07", "match": true , "host": "2001:db8::1" }
|
||||
{"severity":"INFO","time":"2023-08-25T11:31:07.512Z","correlation_id":"01H8P73ABCDEF1234567890ABCD","meta.caller_id":"SessionsController#new","meta.remote_ip":"2001:db8::1","meta.feature_category":"system_access","meta.client_id":"ip/2001:db8::1","message":"Failed Login: username=git ip=2001:db8::1"}
|
||||
# a successful login must not match
|
||||
# failJSON: { "match": false }
|
||||
{"severity":"INFO","time":"2023-08-25T11:55:10.525Z","correlation_id":"01H8P8RPMVW53W0KWEN2H2DNF4","meta.caller_id":"SessionsController#create","meta.remote_ip":"203.0.113.9","meta.feature_category":"system_access","meta.user":"admin","meta.user_id":2,"meta.client_id":"user/2","message":"Successful Login: username=admin ip=203.0.113.9 method=standard admin=true"}
|
||||
|
|
|
|||
|
|
@ -32,4 +32,12 @@
|
|||
2015/01/21 10:58:10 [error] 2833#0: *16336 open() "/var/www/site/cgi-bin/php4" failed (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "HEAD /cgi-bin/php4 HTTP/1.1", host: "1.2.3.4"
|
||||
|
||||
# failJSON: { "time": "2015-01-21T15:02:27", "match": true , "host": "5.7.9.2" }
|
||||
2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"
|
||||
2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"
|
||||
|
||||
# filterOptions: [{"logtype": "journal"}]
|
||||
|
||||
# failJSON: { "match": true, "host": "5.7.9.2", "desc": "systemd journal message, gh-3732" }
|
||||
host nginx[2833]: 2015/01/21 10:56:10 [error] 2833#0: *16336 open() "/var/www/site/cgi-bin/php4" failed (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /cgi-bin/php4 HTTP/1.1", host: "1.2.3.4"
|
||||
|
||||
# failJSON: { "match": true, "host": "5.7.9.2", "desc": "systemd journal message, is not found variant, gh-3732" }
|
||||
host nginx[2833]: 2015/01/21 15:02:27 [error] 2833#0: *16813 "/var/www/site/roundcube/" is not found (2: No such file or directory), client: 5.7.9.2, server: localhost, request: "GET /roundcube/ HTTP/1.1", host: "1.2.3.4"
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ Jan 14 16:18:16 xxx postfix/smtpd[14933]: warning: host[192.0.2.5]: SASL CRAM-MD
|
|||
# failJSON: { "time": "2004-11-04T09:11:01", "match": true , "host": "192.0.2.152", "user": "admin", "desc": "reason unavailable" }
|
||||
Nov 4 09:11:01 mail postfix/smtpd[1234]: warning: unknown[192.0.2.152]: SASL LOGIN authentication failed: (reason unavailable), sasl_username=admin
|
||||
|
||||
# failJSON: { "match": true , "host": "192.0.2.44", "desc": "hostname does not resolve to address, aggressive only (gh-4078)" }
|
||||
Aug 14 10:00:01 mail postfix/smtpd[1234]: warning: hostname bad.example.com does not resolve to address 192.0.2.44: Name or service not known
|
||||
|
||||
# ---------------------------------------
|
||||
# Test-cases of postfix DDOS mode:
|
||||
# ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -414,4 +414,11 @@ Nov 26 16:47:51 srv sshd[19320]: Disconnected from authenticating user root 192.
|
|||
# failJSON: { "time": "2019-07-08T23:40:17", "match": true , "host": "192.0.2.2", "desc": "RFC 5424 format with extra data and brackets in value (gh-2309)" }
|
||||
<38>1 2019-07-08T17:40:17.954167-04:00 hostname.example.com sshd 55555 - [timeQuality tzKnown="1" isSynced="0"][xxx@123 test="[brackets]"] Failed unknown for invalid user redmond from 192.0.2.2 port 55555 ssh2
|
||||
|
||||
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA field (GHSA-c2mg-2r7g-c3wp)" }
|
||||
<38>1 2019-07-08T17:40:17.954169-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA 2nd field (GHSA-c2mg-2r7g-c3wp)" }
|
||||
<38>1 2019-07-08T17:40:17.954170-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA][BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
|
||||
# failJSON: { "match": false , "desc": "RFC 5424 format, injection attempt on STRUCTURED-DATA, unescaped new-line in PARAM-VALUE (GHSA-c2mg-2r7g-c3wp)" }
|
||||
<38>1 2019-07-08T17:40:17.954171-04:00 hostname.example.com sshd 12345 123 [AAAAAAAAAAAAAAAAAAAAAAAAAA XXX="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
|
||||
|
||||
# addFILE: "sshd-journal"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
|
|||
__license__ = "GPL"
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
|
@ -34,6 +35,7 @@ import unittest
|
|||
from .utils import LogCaptureTestCase
|
||||
|
||||
from .. import protocol
|
||||
from ..server import asyncserver
|
||||
from ..server.asyncserver import asyncore, RequestHandler, loop, AsyncServer, AsyncServerException
|
||||
from ..server.utils import Utils
|
||||
from ..client.csocket import CSocket
|
||||
|
|
@ -203,6 +205,44 @@ class Socket(LogCaptureTestCase):
|
|||
self.assertLogged("Server connection was closed: test errors in poll",
|
||||
"Too many errors - stop logging connection errors", all=True)
|
||||
|
||||
def testSocketActivation(self):
|
||||
# Simulate a socket already bound/listening, handed to us as an
|
||||
# inherited fd (systemd socket activation, sd_listen_fds(3)):
|
||||
presock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
presock.bind(self.sock_name)
|
||||
presock.listen(5)
|
||||
# dup to a fresh fd first (presock.fileno() might already be 3):
|
||||
fd = os.dup(presock.fileno())
|
||||
# keep backup fd to allow for shutdown test
|
||||
self.addCleanup(os.close, fd)
|
||||
presock.close()
|
||||
os.dup2(fd, 3)
|
||||
|
||||
self.addCleanup(os.environ.pop, "LISTEN_PID", None)
|
||||
self.addCleanup(os.environ.pop, "LISTEN_FDS", None)
|
||||
self.addCleanup(lambda: os.path.exists(self.sock_name) and os.remove(self.sock_name))
|
||||
os.environ["LISTEN_PID"] = str(os.getpid())
|
||||
os.environ["LISTEN_FDS"] = "1"
|
||||
|
||||
serverThread = self._createServerThread()
|
||||
# consumed once picked up, so forked actions don't misinterpret them:
|
||||
self.assertNotIn("LISTEN_PID", os.environ)
|
||||
self.assertNotIn("LISTEN_FDS", os.environ)
|
||||
|
||||
client = Utils.wait_for(self._serverSocket, 2)
|
||||
testMessage = ["A", "test", "message"]
|
||||
self.assertEqual(client.send(testMessage), testMessage)
|
||||
|
||||
self.server.stop()
|
||||
self._stopServerThread()
|
||||
self.assertFalse(serverThread.is_alive())
|
||||
# socket file is owned by the service manager, must not be removed:
|
||||
self.assertTrue(os.path.exists(self.sock_name))
|
||||
|
||||
# socket is owned by the service manager, must not be shut down:
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as csock:
|
||||
csock.connect(self.sock_name)
|
||||
|
||||
def testSocketForce(self):
|
||||
open(self.sock_name, 'w').close() # Create sock file
|
||||
# Try to start without force
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
|
||||
__license__ = "GPL-v2+"
|
||||
|
||||
version = "1.1.1.dev1"
|
||||
version = "1.1.2.dev1"
|
||||
|
||||
def normVersion():
|
||||
""" Returns fail2ban version in normalized machine-readable format"""
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ PartOf=iptables.service firewalld.service ip6tables.service ipset.service nftabl
|
|||
Type=simple
|
||||
Environment="PYTHONNOUSERSITE=1"
|
||||
RuntimeDirectory=fail2ban
|
||||
# prevents socket created by fail2ban.socket from being removed
|
||||
RuntimeDirectoryPreserve=yes
|
||||
StateDirectory=fail2ban
|
||||
ExecStart=@BINDIR@/fail2ban-server -xf start
|
||||
# if should be logged in systemd journal, use following line or set logtarget to sysout in fail2ban.local
|
||||
|
|
|
|||
12
files/fail2ban.socket.in
Normal file
12
files/fail2ban.socket.in
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=Fail2Ban Socket
|
||||
Documentation=man:fail2ban(1)
|
||||
|
||||
[Socket]
|
||||
ListenStream=%t/fail2ban/fail2ban.sock
|
||||
FileDescriptorName=fail2ban
|
||||
SocketMode=0600
|
||||
RuntimeDirectory=fail2ban
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH FAIL2BAN-CLIENT "1" "April 2024" "Fail2Ban v1.1.1.dev1" "User Commands"
|
||||
.TH FAIL2BAN-CLIENT "1" "August 2026" "Fail2Ban v1.1.2.dev1" "User Commands"
|
||||
.SH NAME
|
||||
fail2ban-client \- configure and control the server
|
||||
.SH SYNOPSIS
|
||||
.B fail2ban-client
|
||||
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
|
||||
.SH DESCRIPTION
|
||||
Fail2Ban v1.1.1.dev1 reads log file that contains password failure report
|
||||
Fail2Ban v1.1.2.dev1 reads log file that contains password failure report
|
||||
and bans the corresponding IP addresses using firewall rules.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
|
|
@ -125,8 +125,10 @@ server
|
|||
.TP
|
||||
\fBstatus \fB\-\-all\fR [FLAVOR]\fR
|
||||
gets the current status of all
|
||||
jails, with optional output style [FLAVOR].
|
||||
Flavors: 'basic' (default), 'cymru', 'short', 'stats'.
|
||||
jails, with optional output style
|
||||
[FLAVOR]. Flavors: 'basic'
|
||||
(default), 'cymru', 'short',
|
||||
\&'stats'
|
||||
.TP
|
||||
\fBstat[istic]s\fR
|
||||
gets the current statistics of all
|
||||
|
|
@ -214,9 +216,11 @@ stops the jail <JAIL>. The jail is
|
|||
removed
|
||||
.TP
|
||||
\fBstatus <JAIL> [FLAVOR]\fR
|
||||
gets the current status of all
|
||||
jails, with optional output style [FLAVOR].
|
||||
Flavors: 'basic' (default), 'cymru', 'short', 'stats'.
|
||||
gets the current status of <JAIL>,
|
||||
with optional output style
|
||||
[FLAVOR]. Flavors: 'basic'
|
||||
(default), 'cymru', 'short',
|
||||
\&'stats'
|
||||
.IP
|
||||
JAIL CONFIGURATION
|
||||
.TP
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH FAIL2BAN-PYTHON "1" "April 2024" "fail2ban-python 1.1.1.1" "User Commands"
|
||||
.TH FAIL2BAN-PYTHON "1" "August 2026" "fail2ban-python 1.1.2.1" "User Commands"
|
||||
.SH NAME
|
||||
fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used
|
||||
.SH DESCRIPTION
|
||||
usage: fail2ban\-python [option] ... [\-c cmd | \fB\-m\fR mod | file | \fB\-]\fR [arg] ...
|
||||
Options (and corresponding environment variables):
|
||||
\fB\-b\fR : issue warnings about str(bytes_instance), str(bytearray_instance)
|
||||
\fB\-b\fR : issue warnings about converting bytes/bytearray to str and comparing
|
||||
.IP
|
||||
and comparing bytes/bytearray with str. (\fB\-bb\fR: issue errors)
|
||||
bytes/bytearray with str or bytes with int. (\fB\-bb\fR: issue errors)
|
||||
.PP
|
||||
\fB\-B\fR : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
|
||||
\fB\-c\fR cmd : program passed in as string (terminates option list)
|
||||
|
|
@ -21,7 +21,7 @@ debug builds); also PYTHONDEBUG=x
|
|||
.IP
|
||||
if stdin does not appear to be a terminal; also PYTHONINSPECT=x
|
||||
.PP
|
||||
\fB\-I\fR : isolate Python from the user's environment (implies \fB\-E\fR and \fB\-s\fR)
|
||||
\fB\-I\fR : isolate Python from the user's environment (implies \fB\-E\fR, \fB\-P\fR and \fB\-s\fR)
|
||||
\fB\-m\fR mod : run library module as a script (terminates option list)
|
||||
\fB\-O\fR : remove assert and __debug__\-dependent statements; add .opt\-1 before
|
||||
.IP
|
||||
|
|
@ -31,9 +31,12 @@ if stdin does not appear to be a terminal; also PYTHONINSPECT=x
|
|||
.IP
|
||||
\&.pyc extension
|
||||
.PP
|
||||
\fB\-P\fR : don't prepend a potentially unsafe path to sys.path
|
||||
\fB\-P\fR : don't prepend a potentially unsafe path to sys.path; also
|
||||
.IP
|
||||
PYTHONSAFEPATH
|
||||
.PP
|
||||
\fB\-q\fR : don't print version and copyright messages on interactive startup
|
||||
\fB\-s\fR : don't add user site directory to sys.path; also PYTHONNOUSERSITE
|
||||
\fB\-s\fR : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x
|
||||
\fB\-S\fR : don't imply 'import site' on initialization
|
||||
\fB\-u\fR : force the stdout and stderr streams to be unbuffered;
|
||||
.IP
|
||||
|
|
@ -57,9 +60,10 @@ also PYTHONWARNINGS=arg
|
|||
.IP
|
||||
control how Python invalidates hash\-based .pyc files
|
||||
.PP
|
||||
\fB\-\-help\-env\fR : print help about Python environment variables and exit
|
||||
\fB\-\-help\-xoptions\fR : print help about implementation\-specific \fB\-X\fR options and exit
|
||||
\fB\-\-help\-all\fR : print complete help information and exit
|
||||
\fB\-\-help\-env\fR: print help about Python environment variables and exit
|
||||
\fB\-\-help\-xoptions\fR: print help about implementation\-specific \fB\-X\fR options and exit
|
||||
\fB\-\-help\-all\fR: print complete help information and exit
|
||||
.PP
|
||||
Arguments:
|
||||
file : program read from script file
|
||||
\- : program read from stdin (default; interactive mode if a tty)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH FAIL2BAN-REGEX "1" "June 2025" "fail2ban-regex 1.1.1.dev1" "User Commands"
|
||||
.TH FAIL2BAN-REGEX "1" "August 2026" "fail2ban-regex 1.1.2.dev1" "User Commands"
|
||||
.SH NAME
|
||||
fail2ban-regex \- test Fail2ban "failregex" option
|
||||
.SH SYNOPSIS
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH FAIL2BAN-SERVER "1" "April 2024" "Fail2Ban v1.1.1.dev1" "User Commands"
|
||||
.TH FAIL2BAN-SERVER "1" "August 2026" "Fail2Ban v1.1.2.dev1" "User Commands"
|
||||
.SH NAME
|
||||
fail2ban-server \- start the server
|
||||
.SH SYNOPSIS
|
||||
.B fail2ban-server
|
||||
[\fI\,OPTIONS\/\fR]
|
||||
.SH DESCRIPTION
|
||||
Fail2Ban v1.1.1.dev1 reads log file that contains password failure report
|
||||
Fail2Ban v1.1.2.dev1 reads log file that contains password failure report
|
||||
and bans the corresponding IP addresses using firewall rules.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
||||
.TH FAIL2BAN-TESTCASES "1" "April 2024" "fail2ban-testcases 1.1.1.dev1" "User Commands"
|
||||
.TH FAIL2BAN-TESTCASES "1" "August 2026" "fail2ban-testcases 1.1.2.dev1" "User Commands"
|
||||
.SH NAME
|
||||
fail2ban-testcases \- run Fail2Ban unit-tests
|
||||
.SH SYNOPSIS
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -80,7 +80,7 @@ class install_scripts_f2b(install_scripts):
|
|||
except: # pragma: no cover
|
||||
print('WARNING: Cannot find root-base option, check the bin-path to fail2ban-scripts in "fail2ban.service" and "fail2ban-openrc.init".')
|
||||
|
||||
scripts = ['fail2ban.service', 'fail2ban-openrc.init']
|
||||
scripts = ['fail2ban.service', 'fail2ban.socket', 'fail2ban-openrc.init']
|
||||
for script in scripts:
|
||||
print(('Creating %s/%s (from %s.in): @BINDIR@ -> %s' % (buildroot, script, script, install_dir)))
|
||||
with open(os.path.join(source_dir, 'files/%s.in' % script), 'r') as fn:
|
||||
|
|
@ -294,5 +294,6 @@ if sys.argv[1] == "install":
|
|||
print("They are in \"/etc/fail2ban/\".")
|
||||
print("")
|
||||
print("You can also install systemd service-unit file from \"build/fail2ban.service\"")
|
||||
print("(optionally paired with \"build/fail2ban.socket\" to enable socket activation)")
|
||||
print("resp. corresponding init script from \"files/*-initd\".")
|
||||
print("")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue