Created
December 16, 2024 19:19
-
-
Save WebDragon/6f954d18b411f0e6aa77b1a21e210f32 to your computer and use it in GitHub Desktop.
inspect /var/log/secure looking for connection attempts happening in egregious numbers and report those counts so we can proactively block them and also look them up in GEOIP bulk lookup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use warnings; | |
use strict; | |
use v5.16; | |
use DateTime qw(); | |
use Regexp::Common qw(net); | |
use Sort::Key::IPv4 qw(ipv4sort); | |
# default daysago = 1, mincount = 80 | |
my ( $daysago, $min, $monthdate, %addrs, $verbose, $help, $usage ) = ( 1, 80 ); | |
use Getopt::Long; | |
Getopt::Long::Configure ("bundling_values"); | |
use Pod::Usage; | |
GetOptions( | |
"m|min=i" => \$min, | |
"d|daysago=i" => \$daysago, | |
"help|?" => \$help, | |
verbose => \$verbose, | |
usage => \$usage, | |
) or warn ("error in commandline arguments") && pod2usage(2); | |
pod2usage(1) if $help; | |
pod2usage(-exitval=>0, -verbose => 2) if $usage; | |
$monthdate = | |
DateTime | |
->now( time_zone => "local" ) | |
->set_time_zone( "floating" ) | |
->truncate( to => "day" ) | |
->subtract( days => $daysago ) | |
->strftime( "%b %e"); | |
our @ARGV = do { | |
opendir my $dh, "/var/log" or die $!; | |
map { "/var/log/$_" } grep /^secure(?:\-\d+)?$/, readdir $dh; | |
}; | |
while (<ARGV>) { | |
chomp; | |
next unless m/$monthdate/; | |
next unless m/Connection closed by|Disconnected from/; | |
if ( my ( $ip ) = $_ =~ m/($RE{net}{IPv4})/ ) { | |
++$addrs{$ip}; | |
} else { | |
warn( "Line matched, but did not contain IPv4 address: '$_'\n"); | |
} | |
} | |
my ($i, $c, $m); | |
format STDOUT_VERBOSE_TOP = | |
attempt | |
ip address count whether address already exists in csf.deny, and how | |
----------------------------------------------------------------------------------------------- | |
. | |
format STDOUT_VERBOSE = | |
@<<<<<<<<<<<<<<<< @<<<<< @* | |
$i, $c, $m | |
. | |
format STDOUT_PLAIN_TOP = | |
attempt | |
ip address count | |
-------------------------- | |
. | |
format STDOUT_PLAIN = | |
@<<<<<<<<<<<<<<<< @<<<<< | |
$i, $c | |
. | |
if ($verbose) { | |
$^ = 'STDOUT_VERBOSE_TOP'; | |
$~ = 'STDOUT_VERBOSE'; | |
} else { | |
$^ = 'STDOUT_PLAIN_TOP'; | |
$~ = 'STDOUT_PLAIN'; | |
} | |
foreach my $key ( ipv4sort keys %addrs ) { | |
next unless $addrs{$key} > $min; | |
my $matched = ( grep /\b\Q$key\E\b/, do { local @ARGV = "/etc/csf/csf.deny"; <> })[0] // 'not found'; | |
$i = $key; | |
$c = $addrs{$key}; | |
$m = $matched; | |
unless ($verbose) { | |
next unless $matched =~ m/not found/; | |
next if $matched =~ m/persistent/; | |
} | |
write; | |
} | |
__END__ | |
=head1 NAME | |
sshdsecure | |
=head1 SYNOPSIS | |
sshdsecure --min 80 --daysago 1 --verbose | |
Options: | |
-m N, --min N mimimum number of attempts on ssh login | |
-d N, --daysago N how many days ago to check the logs for instances | |
--verbose do not filter the output results for | |
-h, --help show the brief help | |
--usage full documentation | |
=head1 OPTIONS | |
=over 8 | |
=item B<-m N, --min N> | |
Only show results whose count of incursion attempts is above N (number), defaults to 80. | |
=item B<-d N, --daysago N> | |
Show counts from N dasy ago (usually limited by how far back /var/log/secure* records go, | |
typically 30 days, defaults to 1 day ago. | |
=item B<--verbose> | |
Do not filter the output results for known entities already captured in csf.deny. | |
The filter in this script is hardcoded to match a consistent string we use in our csf.deny | |
'do not delete' entry, identifying the ssh root attempt or a spammer. | |
Yes, we've already disabled password authentication for root, AND disabled password | |
authentication eeverywhere else and made things ssh-keys only, but were curious how | |
much traffic was still attempting connection in spite of that, and in keeping with | |
earlier decisions/realizations that hey, these are still compromised machines, we | |
should probably prevent them from trying other skulduggery against our VPS hosted sites, | |
and so decided to block them when they go egregiously beyond any normal count of attempts, | |
particularly when LFD (Login Failure Daemon) has failed to auto-block them already | |
(indicating a wilful persistence on their part to distribute things beyond LFD's limits), | |
and look for patterns of interest. | |
=item B<-h, --help> | |
Prints a brief help message and exits. | |
=item B<--usage> | |
Prints the manual page and exits. | |
=back | |
=head1 DESCRIPTION | |
This program is meant to examine /var/log/secure for recent activity against SSHD, | |
and show us patterns of more egregious attempts to connect to it from outside, given | |
that we have now completely disabled password authentication and can no longer check | |
for attempts against the root user as easily as before and that efforts to evade LFD | |
detection have stepped up recently. | |
originally from the following one-liner(s): | |
perl -MDateTime -MRegexp::Common=net -MSort::Key::IPv4=ipv4sort -lnE ' BEGIN { | |
our ($monthdate, %addrs) = DateTime ->now( time_zone => "local") | |
->set_time_zone("floating") ->truncate( to => "day" ) ->subtract( days => 1 ) | |
->strftime("%b %e"); } if (m/^\Q$monthdate/) { next unless | |
m/Connection closed by|Disconnected from/; if ( my ($ip) = $_ =~ | |
m/($RE{net}{IPv4})/ ) { $addrs{$ip}++; } } END { foreach my $key (ipv4sort keys | |
%addrs) { say "$key => $addrs{$key}" unless $addrs{$key} < 80 ; } }' /var/log/secure* | |
for i in $(perl -MDateTime -MRegexp::Common=net -MSort::Key::IPv4=ipv4sort -lnE | |
' BEGIN { our ($monthdate, %addrs) = DateTime ->now( time_zone => "local") | |
->set_time_zone("floating") ->truncate( to => "day" ) ->subtract( days => 1 ) | |
->strftime("%b %e"); } if (m/^\Q$monthdate/) { next unless | |
m/Connection closed by|Disconnected from/; if ( my ($ip) = $_ =~ | |
m/($RE{net}{IPv4})/ ) { $addrs{$ip}++; } } END { foreach my $key (ipv4sort keys | |
%addrs) { say "$key => $addrs{$key}" unless $addrs{$key} < 80 ; } }' | |
/var/log/secur* |sed 's/ .*//'); do grep -q $i /etc/csf/csf.deny || echo $i; done | |
=head1 AUTHOR | |
Scott R. Godin L<MAD House Graphics|https://madhousegraphics.com/> | |
=head1 LICENSE | |
Licensed under the same terms as Perl. | |
=cut | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment