From 0506ad84c9773b17a79454857a365222cb33377b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Gertz?= Date: Sun, 1 Dec 2019 00:03:33 +0100 Subject: [PATCH] Add new_ipv4 constructor that build NetAddr::IP object Prevent read of /etc/resolv.conf by gethostbyname in each NetAddr::IP->new call Only ban the ip on specified port and protocol Ban full ip on protocol with more than 5 port tried Compare length as numeric to have intended proper output Cleanup --- blacklist | 153 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 123 insertions(+), 30 deletions(-) diff --git a/blacklist b/blacklist index 6e3f5c6..e950ba0 100755 --- a/blacklist +++ b/blacklist @@ -5,13 +5,21 @@ use warnings; use IPC::System::Simple qw(capturex); use Data::Validate::IP qw(is_ipv4 is_ipv6); -use NetAddr::IP; +use NetAddr::IP::Util qw(shiftleft inet_4map6 ipv4to6); +use NetAddr::IP qw(:nofqdn Ones); +#IP v4 hash my %ip4s = (); +#IP v6 hash my %ip6s = (); +#Blacklist v4 array my @blrule4s = (); +#Blacklist v6 array my @blrule6s = (); -my %whitelist = ( + +#IP whitelist +#my $iplist = qr/^(?:127\.|::1|2a01:4f8:190:22a6:|5\.9\.143\.173|85\.68\.|81\.67\.|89\.157\.|82\.241\.255\.46)/; +my %iplist = ( ipv4 => [ # Localhost '127.0.0.0/8', @@ -29,78 +37,163 @@ my %whitelist = ( '2a01:4f8:191:1405::/64' ] ); + +#Create a new NetAddr::IP object without calling slow gethostbyname (load /etc/resolv.conf) +sub new_ipv4($) { + #Extract ip and mask + my ($ip, $mask) = split('/', shift); + #Build base struct + my $self = { + #Set mask + mask => !defined($mask)||$mask==32?Ones:shiftleft(Ones, 32 - $mask), + #Mark as ipv4 + isv6 => 0, + #Generate fake address + #XXX: NetAddr::IP expect a faked Socket6 gethostbyname struct + #XXX: see /usr/lib64/perl5/vendor_perl/NetAddr/IP/Util.pm +235 + #addr => scalar ($ip, '', AF_INET, 16, NetAddr::IP::Util::inet_4map6(NetAddr::IP::Util::ipv4to6(pack('C4', split('\.', $ip))))) + addr => inet_4map6(ipv4to6(pack('C4', split('\.', $ip)))) + }; + #Return fake NetAddr::IP object + return bless $self, 'NetAddr::IP'; +} + +#User whitelist my @userlist = ('rapsys'); -# Extract sshd.service scan +#Extract sshd.service scan #map { # # Extract user and ip # if (/Failed password for (?:invalid user )?(.+) from (.+) port [0-9]+ ssh2/ && grep($_ ne $1, @userlist)) { # # Save ip # my $ip = $2; # # Check if v4 ip and not in whitelist -# if (is_ipv4($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv4}}) { +# if (is_ipv4($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv4}}) { # # Add ip in v4 blacklist # $ip4s{$ip}=1; # # Check if v6 ip -# } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv6}}) { +# } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv6}}) { # $ip6s{$ip}=1; # } # } #} capturex('journalctl', '-u', 'sshd.service'); -# -# Extract kernel port scan + +#Extract kernel port scan map { - if (/kernel: net-fw DROP .* SRC=([^\s]+) DST=.*/) { + #oct. 04 19:10:30 aurae.aoihime.eu kernel: net-fw DROP IN=enp3s0 OUT= MAC=50:46:5d:a1:a1:85:0c:86:10:f5:c6:4b:08:00 SRC=61.227.52.153 DST=144.76.27.210 LEN=52 TOS=0x00 PREC=0x00 TTL=116 ID=29123 DF PROTO=TCP SPT=64349 DPT=445 WINDOW=8192 RES=0x00 SYN URGP=0 + if (/kernel: net-fw DROP .* SRC=([^\s]+) .* PROTO=([^\s]+) .* DPT=([^\s]+)/) { # Save ip my $ip = $1; + # Save proto + my $proto = lc($2); + # Save dpt + my $dpt = $3; # Check if v4 ip and not in whitelist - if (is_ipv4($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv4}}) { - $ip4s{$ip}=1; - } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv6}}) { - $ip6s{$ip}=1; + if (is_ipv4($ip) && not scalar map { my $network = new_ipv4($_); my $netip = new_ipv4($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv4}}) { + if (!defined $ip4s{$ip}) { + %{$ip4s{$ip}} = ('tcp' => {}, 'udp' => {}); + } + $ip4s{$ip}{$proto}{$dpt}=1; + } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv6}}) { + if (!defined $ip6s{$ip}) { + %{$ip6s{$ip}} = ('tcp' => {}, 'udp' => {}); + } + $ip6s{$ip}{$proto}{$dpt}=1; } - } elsif (/op=PAM:authentication grantors=\? acct="(.+)" exe="\/usr\/(?:libexec\/dovecot\/auth|sbin\/sshd)" hostname=.+ addr=(.+) terminal=(?:dovecot|ssh) res=failed/ && grep($_ ne $1, @userlist)) { + print $ip."\n"; + #oct. 04 19:17:10 aurae.aoihime.eu kernel: audit: type=1100 audit(1570209430.543:17321294): pid=5890 uid=0 auid=4294967295 ses=4294967295 msg='op=PAM:authentication grantors=? acct="root" exe="/usr/sbin/sshd" hostname=195.154.112.70 addr=195.154.112.70 terminal=ssh res=failed' + } elsif (/op=PAM:authentication grantors=\? acct="(.+)" exe="\/usr\/(?:libexec\/dovecot\/auth|sbin\/sshd)" hostname=.+ addr=(.+) terminal=(dovecot|ssh) res=failed/ && grep($_ ne $1, @userlist)) { # Save ip my $ip = $2; + # Save proto + my $proto = 'tcp'; + # Save dpt + my $dpt = $3 eq 'ssh' ? 22 : 445; # Check if v4 ip and not in whitelist - if (is_ipv4($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv4}}) { + if (is_ipv4($ip) && not scalar map { my $network = new_ipv4($_); my $netip = new_ipv4($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv4}}) { + if (!defined $ip4s{$ip}) { + %{$ip4s{$ip}} = ('tcp' => {}, 'udp' => {}); + } # Add ip in v4 blacklist - $ip4s{$ip}=1; + $ip4s{$ip}{$proto}{$dpt}=1; # Check if v6 ip - } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$whitelist{ipv6}}) { - $ip6s{$ip}=1; + } elsif (is_ipv6($ip) && not scalar map { my $network = NetAddr::IP->new($_); my $netip = NetAddr::IP->new($ip); unless ($network->contains($netip)) { (); } } @{$iplist{ipv6}}) { + if (!defined $ip6s{$ip}) { + %{$ip6s{$ip}} = ('tcp' => {}, 'udp' => {}); + } + $ip6s{$ip}{$proto}{$dpt}=1; + } + #nov. 30 15:30:07 aurae.aoihime.eu kernel: audit: type=1100 audit(1575124207.371:38129): pid=685 uid=985 auid=4294967295 ses=4294967295 msg='op=PAM:authentication grantors=? acct="toto" exe="/usr/bin/pwauth" hostname=? addr=? terminal=? res=failed' + #XXX: Until mod_authnz pass to pwauth the (SERVER_NAME|SERVER_ADDR) + REMOTE_ADDR+REMOTE_PORT in env it's impossible to know who did a failed auth + #XXX: see https://github.com/phokz/mod-auth-external/blob/master/mod_authnz_external/TODO + #} elsif (/op=PAM:authentication grantors=\? acct="(.+)" exe="\/usr\/bin\/pwauth" hostname=.+ addr=(.+) terminal=\? res=failed/ && grep($_ ne $1, @userlist)) { + # ... + } +} capturex('journalctl', '-m', '-t', 'kernel', '-o', 'cat', '--no-hostname'); + +#Process each ipv4s keys +map { + #Set proto as either tcp or udp + for my $proto (('tcp', 'udp')) { + #Check if branch is empty + if (!scalar keys %{$ip4s{$_}{$proto}}) { + #Prune it + delete $ip4s{$_}{$proto}; } } -} capturex('journalctl', '-m', '-t', 'kernel'); +} keys %ip4s; -# Open blrule4s file for reading +#Process each ipv6s keys +map { + #Set proto as either tcp or udp + for my $proto (('tcp', 'udp')) { + #Check if branch is empty + if (!scalar keys %{$ip6s{$_}{$proto}}) { + #Prune it + delete $ip6s{$_}{$proto}; + } + } +} keys %ip6s; + +#Open blrule4s file for reading open (my $fh, '<', '/etc/shorewall/blrules') or die "Can't open < /etc/shorewall/blrules: $!"; -# Populate with comments +#Populate with comments @blrule4s = map { chomp($_); if (/^#/) { $_; } else { (); } } <$fh>; -# Prepend each specific ip from whitelist -map { push @blrule4s, "WHITELIST\tnet:$1\tall" if (/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/32$/); } @{$whitelist{ipv4}}; +#Prepend each specific ip from whitelist +map { push @blrule4s, "WHITELIST\tnet:$1\tall" if (/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/32$/); } @{$iplist{ipv4}}; -# Build blacklist -map { push @blrule4s, "DROP\t\tnet:".$_.(length lt 12?"\t":'')."\tfw"; } sort keys %ip4s; -# Close blrule4s file +#Build blacklist +map { + #Set proto from hash + for my $proto (keys %{$ip4s{$_}}) { + #Push rule + push @blrule4s, "DROP\t\tnet:".$_.(length($_)<12?"\t":'')."\tfw\t$proto\t".(scalar keys %{$ip4s{$_}{$proto}}>5||defined $ip4s{$_}{$proto}{0}?'#':'').join(",", keys %{$ip4s{$_}{$proto}}); + } +} sort keys %ip4s; + +#Close blrule4s file close $fh or die "Can't close fh: $!"; -# Open blrule4s file for writing +#Open blrule4s file for writing open ($fh, '>', '/etc/shorewall/blrules') or die "Can't open > /etc/shorewall/blrules: $!"; -# Inject content of blacklist +#Inject content of blacklist map { print $fh $_."\n"; } @blrule4s; -# Close blrule4s file +#Close blrule4s file close $fh or die "Can't close fh: $!"; -# Print ipv6 to update hash +#Print ipv6 to update hash #XXX; right now it don't seems scanned at all... for (sort keys %ip6s) { - print $_."\n"; + #Set proto from hash + for my $proto (keys %{$ip6s{$_}}) { + #Print the ipv6 scanner + print $_."\t$proto\t".join(",", keys %{$ip6s{$_}{$proto}})."\n"; + } } # Restart shorewall service -- 2.41.1