Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
BruteForceBlocker v1.2.6 - Nov 3 2018
- add new regexps to match more failure log entries of recent OpenSSH versions
- resolve reverse DNS of blocked IPs

BruteForceBlocker v1.2.5 - Feb 11 2018
- add a new regexp to match another failure log entry
- contributed by Yasuhiro KIMURA

BruteForceBlocker v1.2.4 - Sep 2 2017
- add a new regexp to match failure log entries of recent OpenSSH versions
- contributed by Max Khon
Expand Down
6 changes: 4 additions & 2 deletions CREDITS
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@

Thanks to:

- Jonas Davidsson and Matt Pearce for good ideas
- J.R. Oldroyd for great ideas, he is also one of the reasons why
BruteForceBlocker v1.2 was released
- Branislav Gerzo for some perl coding hints and help with code cleanup
- Kan Sasaki who sent me a patch for BruteForceBlocker which allows to
resolve reverze DNS to an IP address
resolve reverse DNS to an IP address
- Max Khon for regexp to match recent OpenSSH failure log entries
- Yasuhiro KIMURA for regexp to match another failure log entry
- Balazs Mateffy for new regexps to match more failure log entr and
an idea to resolve reverse DNS records for blocked IPs
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BruteForceBlocker v1.2.4
BruteForceBlocker v1.2.6

BruteForceBlocker is a perl script, that works along with pf - OpenBSD's
firewall (which is also available on FreeBSD and NetBSD) and its main
Expand Down
35 changes: 20 additions & 15 deletions bruteforceblocker.pl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
require '/usr/local/etc/bruteforceblocker.conf';

my $work = {
version => '1.2.4',
version => '1.2.6',
ipv4 => '(?:\d{1,3}\.){3}\d{1,3}', # regexp to match ipv4 address
ipv6 => '[\da-fA-F:]+', # regexp to match ipv6 address
fqdn => '[\da-z\-.]+\.[a-z]{2,4}', # regexp to match fqdn
Expand All @@ -35,15 +35,15 @@
}
close(TABLE) or syslog("notice", "Couldn't close $cfg->{tablefile}");

syslog('notice', 'downloading blacklist from project site') if $cfg->{debug};
syslog('notice', 'downloading blacklist from the project site') if $cfg->{debug};

# download the list from project site and load IPs to @remoteIPs array
if ( my $content = download("$work->{projectsite}/blist.php?mindays=$cfg->{mindays}&mincount=$cfg->{mincount}") ) {
while( $content =~ /^($work->{ipv4}|$work->{ipv6})/gm ) {
push(@{$work->{remoteIPs}}, $1);
}
} else {
syslog('notice', "Can't download IP blacklist from project site") if $cfg->{debug};
syslog('notice', "Unable to download IP blacklist from the project site") if $cfg->{debug};
}

# get IPs that we don't have in local pf table
Expand All @@ -67,7 +67,7 @@
syslog('notice', "Couldn't add $work->{pool} to firewall");
}
}
syslog('notice', 'blacklist synchronized with project site') if $cfg->{debug};
syslog('notice', 'blacklist synchronized with the project site') if $cfg->{debug};
}

my %count = (); # hash used to store total number of failed tries
Expand All @@ -78,12 +78,14 @@

while (<>) {
if (/.*Failed password.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*Invalid user.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn})$/i ||
/.*Did not receive identification string from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn})$/i ||
/.*Bad protocol version identification .* from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn})$/i ||
/.*Failed keyboard.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*Invalid user.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*Did not receive identification string from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*Bad protocol version identification .* from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*User.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) not allowed because.*/i ||
/.*error: maximum authentication attempts exceeded for.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}).*/i ||
/.*fatal: Unable to negotiate with ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}).*/i) {
/.*error: maximum authentication attempts exceeded for.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*error: PAM: authentication error for.*from ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i ||
/.*Unable to negotiate with ($work->{ipv4}|$work->{ipv6}|$work->{fqdn}) port.*/i) {

my $IP = $1;
if ($IP =~ /$work->{fqdn}/i) {
Expand Down Expand Up @@ -124,8 +126,11 @@ sub download {
sub block {
my ($IP) = shift or die "Need IP!\n";

my $query = $res->search($IP, "PTR");
my $RDNS = $query ? ($query->answer)[0]->ptrdname : "not resolved";

if ($timea{$IP} && ($timea{$IP} < time - $cfg->{timeout})) {
syslog('notice', "resetting $IP count, since it wasn't active for more than $cfg->{timeout} seconds") if $cfg->{debug};
syslog('notice', "resetting $IP ($RDNS) count, since it wasn't active for more than $cfg->{timeout} seconds") if $cfg->{debug};
delete $count{$IP};
}
$timea{$IP} = time;
Expand All @@ -134,11 +139,11 @@ sub block {
$count{$IP}++;

if ($cfg->{debug} && ($count{$IP} < $cfg->{max_attempts}+1)) {
syslog('notice', "$IP was logged with total count of $count{$IP} failed attempts");
syslog('notice', "$IP ($RDNS) was logged with total count of $count{$IP} failed attempts");
}

if ($count{$IP} == $cfg->{max_attempts}+1) {
syslog('notice', "IP $IP reached maximum number of failed attempts!") if $cfg->{debug};
syslog('notice', "IP $IP ($RDNS) reached maximum number of failed attempts!") if $cfg->{debug};
if (!grep { /$IP/ } @{$cfg->{whitelist}}) {
$work->{pool} = $IP . '/32' if ($IP =~ /\./); # block whole ipv4 pool
$work->{pool} = $IP . '/128' if ($IP =~ /\:/); # block while ipv6 pool
Expand All @@ -149,14 +154,14 @@ sub block {
syslog('notice', "Couldn't add $cfg->{pool} to firewall");
system("$cfg->{pfctl} -k $IP") == 0 ||
syslog('notice', "Couldn't kill all states for $IP");
system("echo '$work->{pool}\t\t# $work->{timea}' >> $cfg->{tablefile}") == 0 ||
system("echo '$work->{pool}\t\t# $work->{timea} ($RDNS)' >> $cfg->{tablefile}") == 0 ||
syslog('notice', "Could't write $work->{pool} to $cfg->{table}'s table file");

# send mail if it is configured
if ($cfg->{email} && $cfg->{email} ne '') {
syslog('notice', "sending email to $cfg->{email}") if $cfg->{debug};
open(MAIL, "| $cfg->{mail} -s '$work->{hostname}: BruteForceBlocker blocking $work->{pool}' $cfg->{email}");
print (MAIL "BruteForceBlocker blocking $work->{pool} in pf table $cfg->{table}\n");
open(MAIL, "| $cfg->{mail} -s '$work->{hostname}: BruteForceBlocker blocking $work->{pool} ($RDNS)' $cfg->{email}");
print (MAIL "BruteForceBlocker blocking $work->{pool} ($RDNS) in pf table $cfg->{table}\n");
close(MAIL);
}
;
Expand Down