2019-11-23 03:40:23 +00:00
|
|
|
# ban2fail
|
|
|
|
|
|
|
|
(C) 2019 John D. Robertson <john@rrci.com>
|
|
|
|
|
2019-12-04 16:19:23 +00:00
|
|
|
**ban2fail** is a simple and efficient tool to coordinate log file scanning,
|
|
|
|
reporting, and iptables filtering. As the name implies, *ban2fail* was
|
|
|
|
inspired by the popular *fail2ban* project (http://fail2ban.org).
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-24 22:50:11 +00:00
|
|
|
*ban2fail* started with a few hours of frenzied C hacking after my mail server
|
|
|
|
was exploited to deliver spam for others who had cracked a user's SMTP send
|
|
|
|
password. After inspecting the log files I realized that crackers are now using
|
2019-11-27 19:42:58 +00:00
|
|
|
widely distributed attacks, and that I would need an extremely efficient tool
|
|
|
|
that could run in a fraction of a second on my rather modest Linode virtual
|
|
|
|
server to have a chance of stopping them. Here are the timing results for a
|
2019-12-04 16:20:05 +00:00
|
|
|
typical scan on my server:
|
2019-11-25 02:38:45 +00:00
|
|
|
|
|
|
|
```
|
2019-12-06 14:15:49 +00:00
|
|
|
real 0m0.325s
|
|
|
|
user 0m0.186s
|
|
|
|
sys 0m0.150s
|
2019-11-25 02:38:45 +00:00
|
|
|
```
|
|
|
|
|
2019-11-27 19:42:58 +00:00
|
|
|
Currently I am running *ban2fail* from a *systemd* service file which triggers
|
|
|
|
*ban2fail* whenever a watched log file is modified. This gives attackers at
|
|
|
|
most a 0.4 second window to do their worst. I hope you find this code useful.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:25:22 +00:00
|
|
|
## Configuration
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 03:40:23 +00:00
|
|
|
*ban2fail* works from a configuration file found at
|
|
|
|
"/etc/ban2fail/ban2fail.cfg". The overarching premise is that if any REGEX
|
|
|
|
appearing in a LOGTYPE clause matches a line in an associated log file, then by
|
|
|
|
default that IP will be blocked.
|
|
|
|
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
2019-11-23 04:16:05 +00:00
|
|
|
LOGTYPE auth {
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-05 16:06:11 +00:00
|
|
|
# Where to find the log files
|
2019-11-23 04:16:05 +00:00
|
|
|
DIR= /var/log
|
|
|
|
PREFIX= auth.log
|
|
|
|
|
2019-12-05 16:06:11 +00:00
|
|
|
# How to read the timestamp
|
2019-12-04 16:18:00 +00:00
|
|
|
TIMESTAMP auth_ts {
|
2019-12-05 16:06:11 +00:00
|
|
|
# isolates the timestamp from a line matched by a TARGET
|
2019-12-04 16:18:00 +00:00
|
|
|
REGEX= ^(.*) srv
|
2019-12-05 16:06:11 +00:00
|
|
|
# Passed to strptime() to intrepret the timestamp string
|
2019-12-04 16:18:00 +00:00
|
|
|
STRPTIME= %b %d %T
|
2019-12-05 16:06:11 +00:00
|
|
|
# These stamps do not include the year, so it is implied.
|
2019-12-04 16:18:00 +00:00
|
|
|
FLAGS= GUESS_YEAR
|
|
|
|
}
|
|
|
|
|
|
|
|
TARGET imap {
|
2019-12-05 16:06:11 +00:00
|
|
|
# Pattern to search for, isolates the IP address
|
2019-12-04 16:18:00 +00:00
|
|
|
REGEX= imapd.*Login failed.*\[([0-9.a-f:]+)\]$
|
2019-12-05 16:06:11 +00:00
|
|
|
# Assign this as the severity of the offense.
|
2019-12-04 16:18:00 +00:00
|
|
|
SEVERITY= 3
|
|
|
|
}
|
|
|
|
|
|
|
|
TARGET ssh {
|
|
|
|
SEVERITY= 4
|
|
|
|
REGEX= sshd.*Failed password.*from ([0-9.a-f:]+) port [0-9]+ ssh2$
|
|
|
|
REGEX= sshd.*Invalid user.*from ([0-9.a-f:]+) port
|
|
|
|
}
|
|
|
|
|
|
|
|
TARGET negotiate_fail {
|
|
|
|
SEVERITY= 2
|
|
|
|
REGEX= Unable to negotiate with ([0-9.a-f:]+) port
|
|
|
|
}
|
|
|
|
|
|
|
|
TARGET dovecot {
|
|
|
|
SEVERITY= 3
|
2019-12-05 16:06:11 +00:00
|
|
|
REGEX= dovecot.*authentication failure.*rhost=([0-9.a-f:]+)
|
2019-12-04 16:18:00 +00:00
|
|
|
}
|
2019-11-23 04:16:05 +00:00
|
|
|
}
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 03:40:23 +00:00
|
|
|
|
|
|
|
Syntax in the config file is pretty much the same as the nftables syntax. All
|
2019-11-23 15:38:59 +00:00
|
|
|
keywords must be in upper case. Any values in the key=value pairs have
|
2019-11-27 14:44:35 +00:00
|
|
|
whitespace stripped from the beginning and end of the line. Since there is
|
|
|
|
little escaping of characters going on, regular expressions are mostly WYSIWYG.
|
2019-12-06 14:15:49 +00:00
|
|
|
If you have a hash symbol '#' or a double quote '"' in your pattern (which are
|
|
|
|
special characters for the config file parser), you will need to escape
|
|
|
|
them like so:
|
2019-11-27 14:44:35 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
# Nov 27 02:03:03 srv named[764]: client @0x7fe6a0053420 1.192.90.183#27388 (www.ipplus360.com): query (cache) 'www.ipplus360.com/A/IN' denied
|
|
|
|
REGEX= named.*client.* ([0-9.a-f:]+)\#.*denied$
|
|
|
|
```
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 15:46:02 +00:00
|
|
|
Finding typos and so forth in the config file is easy; use the -v command flag
|
|
|
|
to print all unrecognized content (besides comments).
|
|
|
|
|
|
|
|
`ban2fail -v`
|
|
|
|
|
2019-11-23 03:40:23 +00:00
|
|
|
The only way to alter the default blocking behavior is with a MAX\_OFFENSES
|
|
|
|
clause. This clause allows you specify how many offenses are tolerated before an
|
|
|
|
IP is blocked. Offenses will naturally disappear as old logfiles are deleted by
|
|
|
|
*logrotate*.
|
|
|
|
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
2019-12-04 16:18:00 +00:00
|
|
|
# Whitelist ourself
|
|
|
|
MAX_OFFENSES -1 {
|
|
|
|
# Put your server's IP addresses here
|
|
|
|
# IP= 1.2.3.4
|
|
|
|
IP= 127.0.0.1
|
|
|
|
# IP= dead:beef::20::32a
|
|
|
|
IP= ::1
|
2019-11-23 04:16:05 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 16:18:00 +00:00
|
|
|
# Allegedly legit servers
|
|
|
|
MAX_OFFENSES 50 {
|
|
|
|
|
|
|
|
# Google Ireland
|
|
|
|
IP= 2a00:1450:4864:20::32a
|
|
|
|
IP= 2a00:1450:4864:20::336
|
|
|
|
|
|
|
|
# Google EU
|
|
|
|
# Attempted to break in
|
|
|
|
# IP= 35.205.240.168
|
|
|
|
|
|
|
|
# Google US
|
|
|
|
IP= 09.85.216.42
|
|
|
|
# Attempted to break in
|
|
|
|
# IP= 130.211.246.128
|
|
|
|
IP= 209.85.166.194
|
|
|
|
IP= 209.85.166.195
|
|
|
|
IP= 209.85.208.67
|
|
|
|
IP= 209.85.214.194
|
|
|
|
IP= 209.85.215.173
|
|
|
|
IP= 209.85.215.175
|
|
|
|
IP= 209.85.215.193
|
|
|
|
IP= 209.85.216.42
|
|
|
|
IP= 2607:f8b0:4864:20::1034
|
|
|
|
IP= 2607:f8b0:4864:20::a46
|
|
|
|
|
|
|
|
# Yahoo
|
|
|
|
IP= 106.10.244.139
|
|
|
|
|
|
|
|
# Outlook
|
|
|
|
IP= 40.92.4.30
|
|
|
|
IP= 40.107.73.61
|
|
|
|
IP= 40.107.74.48
|
|
|
|
IP= 40.107.74.72
|
|
|
|
IP= 40.107.76.74
|
|
|
|
IP= 40.107.79.52
|
|
|
|
IP= 40.107.79.59
|
|
|
|
IP= 40.107.80.40
|
|
|
|
IP= 40.107.80.53
|
|
|
|
IP= 40.107.80.78
|
|
|
|
IP= 40.107.82.75
|
|
|
|
IP= 52.101.129.30
|
|
|
|
IP= 52.101.132.108
|
|
|
|
IP= 52.101.136.79
|
|
|
|
IP= 52.101.140.230
|
2019-11-23 04:16:05 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 16:18:00 +00:00
|
|
|
# "trusted" addresses
|
|
|
|
MAX_OFFENSES 200 {
|
2019-11-23 04:16:05 +00:00
|
|
|
|
|
|
|
# me from home
|
2019-12-04 16:18:00 +00:00
|
|
|
# IP= 1.2.3.4/20
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-12-04 16:18:00 +00:00
|
|
|
# Customer
|
|
|
|
# IP= 5.6.7.8/24
|
2019-11-23 04:16:05 +00:00
|
|
|
}
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
2019-11-23 03:40:23 +00:00
|
|
|
|
|
|
|
If you recieve a complaint about an address unjustly getting blocked, place it
|
|
|
|
in one of the MAX\_OFFENSES blocks, and the IP will be unblocked the next time
|
2019-11-23 15:55:06 +00:00
|
|
|
*ban2fail* runs in production mode.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:25:22 +00:00
|
|
|
## Working with *ban2fail*
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 03:40:23 +00:00
|
|
|
There are two primary modes in which *ban2fail* is used:
|
|
|
|
|
|
|
|
* Production mode, where iptables rules are modified.
|
|
|
|
|
2019-11-25 14:15:14 +00:00
|
|
|
* Testing mode, where modifications to blocking rules are merely indicated.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:25:22 +00:00
|
|
|
### Production
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-12-04 16:18:00 +00:00
|
|
|
In production mode it is expected that *ban2fail* is running non-interactively,
|
2019-11-23 03:40:23 +00:00
|
|
|
and no output is printed unless addresses are (un)blocked. It is also possible
|
|
|
|
to generate a listing of addresses, offense counts, and status with the -a
|
|
|
|
command flag. Likewise, a listing of countries and offense counts is available
|
2019-12-04 16:18:00 +00:00
|
|
|
with the *-c* flag. In order to get DNS information for the *-a* flag, follow
|
|
|
|
with a plus for all DNS info *-a+*, or a minus for only legit (backward &
|
|
|
|
forward match) info *-a-*. In the list, DNS issues are presented like so:
|
|
|
|
|
|
|
|
```
|
|
|
|
# DNS is good
|
2019-12-06 14:15:49 +00:00
|
|
|
0 Dec 06 08:31 1/0 offenses AR [BLK] 200.71.237.244 host244.200-71-237.telecom.net.ar
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-05 19:50:01 +00:00
|
|
|
# Reverse lookup failed with DNS server
|
2019-12-06 14:15:49 +00:00
|
|
|
0 Dec 05 19:43 1/0 offenses GB [BLK] 185.217.230.146 SERVFAIL
|
2019-12-05 19:50:01 +00:00
|
|
|
|
|
|
|
# Reverse lookup is a non-existent domain
|
2019-12-06 14:15:49 +00:00
|
|
|
2 Dec 05 21:11 1/0 offenses US [BLK] 67.205.153.94 NXDOMAIN
|
2019-12-05 19:50:01 +00:00
|
|
|
|
|
|
|
# Forward lookup does not match reverse lookup
|
2019-12-06 14:15:49 +00:00
|
|
|
0 Dec 06 08:40 1/0 offenses LU [BLK] 92.38.132.54 ibocke43.monster !
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-05 19:50:01 +00:00
|
|
|
# Forward DNS record does not exist
|
2019-12-06 14:15:49 +00:00
|
|
|
0 Dec 06 08:37 1/0 offenses US [BLK] 63.81.90.135 63-81-90-135.nca.lanset.com !!
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-05 19:50:01 +00:00
|
|
|
# DNS is inconclusive due to lack of response from a DNS server
|
2019-12-06 14:15:49 +00:00
|
|
|
0 Dec 05 22:04 1/0 offenses RU [BLK] 77.221.144.107 news5.burningcoalsa.com ~
|
2019-12-04 16:18:00 +00:00
|
|
|
```
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-12-04 17:13:37 +00:00
|
|
|
If you want to see the offending log lines for specific address(es), supply
|
|
|
|
them on the command line like so:
|
|
|
|
|
|
|
|
```
|
2019-12-06 14:17:59 +00:00
|
|
|
john@srv:~$ ban2fail 68.183.105.52
|
2019-12-06 14:15:49 +00:00
|
|
|
====== Report for 68.183.105.52 ======
|
|
|
|
------- /var/log/auth.log -------------
|
|
|
|
Dec 5 17:50:47 srv sshd[22326]: Invalid user cron from 68.183.105.52 port 41874
|
|
|
|
Dec 5 17:50:48 srv sshd[22326]: Failed password for invalid user cron from 68.183.105.52 port 41874 ssh2
|
2019-12-04 17:13:37 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
|
2019-11-23 04:25:22 +00:00
|
|
|
### Testing
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 15:38:59 +00:00
|
|
|
In test mode (-t flag) the presumption is that you are testing a modified
|
2019-11-23 03:40:23 +00:00
|
|
|
configuration which is not yet in place, and that you don't want to disturb the
|
|
|
|
production setup. This is how you might do that:
|
|
|
|
|
2019-11-23 04:16:05 +00:00
|
|
|
`ban2fail -t myNew.cfg -a`
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:29:27 +00:00
|
|
|
No iptables rules will be modified. You will be shown in the listing which
|
2019-11-23 15:38:59 +00:00
|
|
|
addresses would be (un)blocked if the contents of "myNew.cfg" was in place, and
|
|
|
|
*ban2fail* was running in production mode.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
|
|
|
When you are happy with the new configuration, copy it into place, and the the
|
2019-11-23 15:58:18 +00:00
|
|
|
iptable rule changes will be realized the next time *ban2fail* runs in
|
|
|
|
production mode.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:25:22 +00:00
|
|
|
## Building the Project
|
2019-11-23 04:16:05 +00:00
|
|
|
|
2019-11-23 15:55:06 +00:00
|
|
|
I've tested *ban2fail* only on Debian Buster, but it should compile on just
|
|
|
|
about any modern Linux distro. It uses the following libraries:
|
2019-11-23 15:38:59 +00:00
|
|
|
|
2019-11-27 14:44:35 +00:00
|
|
|
+ *libcrypto* from the libssl package, for md5 checksums
|
2019-11-23 15:38:59 +00:00
|
|
|
|
2019-12-04 16:18:00 +00:00
|
|
|
+ *libGeoIP* to identify the country of origin for IP addresses
|
2019-11-23 15:38:59 +00:00
|
|
|
|
2019-11-23 15:55:06 +00:00
|
|
|
+ *libz* to read compressed log files
|
2019-11-23 15:38:59 +00:00
|
|
|
|
2019-12-06 14:15:49 +00:00
|
|
|
+ *libpthread* for parallel DNS lookups (200 simultaneous)
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-04 17:15:05 +00:00
|
|
|
+ *libdb* caching of offense location and size in log files
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-11-23 15:38:59 +00:00
|
|
|
Build and install like so:
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
2019-11-23 04:16:05 +00:00
|
|
|
make release
|
2019-12-04 16:18:00 +00:00
|
|
|
make install
|
2019-11-23 04:23:18 +00:00
|
|
|
```
|
|
|
|
|
2019-12-06 14:15:49 +00:00
|
|
|
The make *install* target calls *install.sh*, which does a bunch of stuff
|
|
|
|
including setting up and enabling a systemd service, so you might want have a
|
|
|
|
look before pulling the trigger.
|
2019-11-23 03:40:23 +00:00
|
|
|
|
2019-12-06 14:15:49 +00:00
|
|
|
*ban2fail.service* points to *ban2fail.sh*, which can be tested from the command line for debugging. Remember to make sure the service is disabled:
|
|
|
|
```
|
|
|
|
systemctl stop ban2fail
|
|
|
|
```
|
2019-12-04 16:18:00 +00:00
|
|
|
|
2019-12-06 14:15:49 +00:00
|
|
|
In order to run *ban2fail* as non-root user, the user must belong to group
|
|
|
|
'adm'. This is so in order to run iptables, which is accomplished via setuid(0)
|
|
|
|
at the appropriate time.
|
2019-12-04 16:18:00 +00:00
|
|
|
|