Compare commits

...

16 Commits

Author SHA1 Message Date
219b536913 Swap from amazon to hetzner ip 2025-10-05 20:54:36 +02:00
77c046f0c2 split up rejects 2025-10-05 20:53:08 +02:00
68d2e267b2 Add support for exceeded LoginGraceTime 2025-08-14 00:11:46 +02:00
0d1bdf84e1 Add support for timeout before authentication message 2025-07-24 22:58:40 +02:00
058792486c Make db->init() more sensible, config->db_connect is the only one handling the db connection (remote db->init from config->new) 2025-07-06 15:23:44 +02:00
2806ce4948 Remove unneeded logging 2025-07-01 20:15:19 +02:00
22fc70b64c Removing authenticating from regex 2025-07-01 20:13:41 +02:00
5ad45bbb65 Add work ip + SSLlabs 2025-07-01 20:13:14 +02:00
23a4b9abe3 Timeouts aren't hostile (enough) 2025-07-01 20:12:43 +02:00
7b7083adca Disable auto-reconnect, and handle db/dbh centrally-ish 2025-07-01 20:11:57 +02:00
999bc6c8c8 Add ssh.pm to the auto-reconnect-fun 2025-05-27 01:30:50 +02:00
e0bab3a7b4 Initial idea to reconnect if needed 2025-05-27 01:25:36 +02:00
7667819687 Add Timeout/Connection closed during SSL handshake as a hostile action 2025-03-22 23:00:27 +01:00
8cb980a9b9 Add error code 400, as that seems to be a non-good one, also on http 2025-03-21 23:37:43 +01:00
a389912040 Add BADREQ with return code0, and accept anything else that is routed to https 2025-03-20 19:38:04 +01:00
7c418005ae Lessen noise:) 2025-03-20 19:03:24 +01:00
10 changed files with 120 additions and 32 deletions

View File

@@ -18,21 +18,22 @@ sub add {
my $self = shift;
my $params = shift;
die "No params" unless $params;
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
my $dbh = $self->{'config'}->db_connect();
my $return = {};
$return = $self->addtoblacklist($params) if($params->{'list'} eq 'black');
$return = $self->addtorejectlist($params) if($params->{'list'} eq 'reject');
$return = $self->addtoblacklist($dbh, $params) if($params->{'list'} eq 'black');
$return = $self->addtorejectlist($dbh, $params) if($params->{'list'} eq 'reject');
return $return;
}
sub addtoblacklist {
my $self = shift;
my $dbh = shift;
my $work = shift;
my $host = $work->{'host'};
my $asn = $work->{'asn'} || 0;
my $iso = $work->{'iso'} || '';
return { retval => 0, retmsg => "No host specified" } unless($host);
my $sth = $self->{'dbh'}->prepare("
my $sth = $dbh->prepare("
SELECT COUNT(*)
FROM reject_iptables
WHERE ip = ? AND
@@ -40,7 +41,7 @@ sub addtoblacklist {
$sth->execute($host);
my $rows = $sth->fetchrow_arrayref->[0];
unless($rows) {
my $sth = $self->{'dbh'}->prepare("INSERT INTO reject_iptables(ip,asn,iso) VALUES(?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtoblacklist '.DBI::errstr };
my $sth = $dbh->prepare("INSERT INTO reject_iptables(ip,asn,iso) VALUES(?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtoblacklist '.DBI::errstr };
$sth->execute($host,$asn,$iso) || return { retval => 0, retmsg => 'Failed to execute statement in addtoblacklist '.DBI::errstr };
system "/sbin/pfctl -q -t $self->{'block_table'} -T add $host";
system "/sbin/pfctl -q -k $host";
@@ -53,13 +54,14 @@ sub addtoblacklist {
sub addtorejectlist {
my $self = shift;
my $dbh = shift;
my $work = shift;
my $host = $work->{'host'};
my $service = $work->{'service'} || '';
my $asn = $work->{'asn'} || 0;
my $iso = $work->{'iso'} || '';
return { retval => 0, retmsg => "No host specified" } unless($host);
my $sth = $self->{'dbh'}->prepare("INSERT INTO reject(ip,service,asn,iso) VALUES(?,?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtorejectlist '.DBI::errstr };
my $sth = $dbh->prepare("INSERT INTO reject(ip,service,asn,iso) VALUES(?,?,?,?)") || return { retval => 0, retmsg => 'Failed to prepare statement in addtorejectlist '.DBI::errstr };
$sth->execute($host,$service,$asn,$iso) || return { retval => 0, retmsg => 'Failed to executute statement in addtorejectlist '.DBI::errstr };
return { retval => 1, retmsg => "Added to reject list" };
}

View File

@@ -22,6 +22,7 @@ sub new {
$self->parse if($self->load);
$self->{'config'}->{'test'} = $test;
$self->{'config'}->{'parse'} = $parse;
$self->{'db'} = undef;
return $self;
}
@@ -120,16 +121,38 @@ sub get_fetcher_module {
}
}
sub db_connect {
my $self = shift;
my $dbh = $self->get_dbh();
if ($dbh) {
if ($dbh->ping()) {
return $self->get_dbh();
#We can ping, all is good
} else {
#No can ping, time to reconnect
return $self->{'db'}->connect();
}
} else {
#Never connected?
return $self->{'db'}->connect();
}
die "End of db_connect should never be reached";
}
sub set_db {
my $self = shift;
$self->{'db'} = shift;
}
sub get_dbh {
my $self = shift;
my $dbh = $self->{'config'}->{'fetchers'}->{'db'}->{'dbh'};
return $dbh;
return $self->{'db'}->{'dbh'};
}
sub set_dbh {
my $self = shift;
my $dbh = shift;
$self->{'config'}->{'fetchers'}->{'db'}->{'dbh'} = $dbh;
$self->{'db'}->{'dbh'} = $dbh;
return 1;
}

View File

@@ -2,7 +2,6 @@ package My::parser::db;
use strict;
use warnings;
use DBI;
#use Scalar::Util qw(weaken);
sub new {
my $class = shift;
@@ -18,9 +17,7 @@ sub new {
sub init {
my $self = shift;
my $parser = shift; ## not needed for this module as the db-connection is for all modules
$self->connect || die "Could not connect to db";
return 1;
return $self->{'config'}->get_dbh();
}
sub connect {
@@ -32,13 +29,19 @@ sub connect {
my $db = $config->get_as_single_val('config','db');
my $dbh;
if($usr && $pwd && $host && $db) {
my $logstr = 'Connecting to DB...';
my $i = 0;
while(1) {
last if($dbh = DBI->connect("DBI:mysql:database=$db;host=$host",$usr,$pwd, { PrintError => 1, mysql_auto_reconnect=>1, AutoCommit => 1 }));
sleep(10);
last if($dbh = DBI->connect("DBI:mysql:database=$db;host=$host",$usr,$pwd, { PrintError => 1, mysql_auto_reconnect=>0, AutoCommit => 1 }));
sleep($i);
$i++;
unless ($i % 10) {
$self->{'config'}->{'logger'}->log($logstr.'timed out, retry #'.$i);
}
}
$config->set_dbh($dbh);
#$self->{'config'}->{'logger'}->log("Sucsessfully connected to db"); FIXME add debug to config?
return 1;
$self->{'config'}->{'logger'}->log($logstr.'done');
return $config->get_dbh();
} else {
$self->{'config'}->{'logger'}->log("Unable to connect to db, not enough parameters");
return 0;

View File

@@ -48,9 +48,25 @@ sub parser {
m/(\ \[($re_host)\]\ )/gcix && do {
$host = $2;
};
} elsif($string =~ m/\ rejected (AUTH|MAIL)\ /) {
} elsif($string =~ m/\ rejected (EHLO|HELO)\ /) {
$_ = $string;
$reply = 'Rejected MAIL or AUTH';
$reply = 'Rejected HELO/EHLO';
$hostile = 1;
PARSER:
m/(\ \[($re_host)\](\:|\ ))/gcix && do {
$host = $2;
};
} elsif($string =~ m/\ rejected AUTH\ /) {
$_ = $string;
$reply = 'Rejected AUTH';
$hostile = 1;
PARSER:
m/(\ \[($re_host)\]\ )/gcix && do {
$host = $2;
};
} elsif($string =~ m/\ rejected MAIL\ /) {
$_ = $string;
$reply = 'Rejected MAIL';
$hostile = 1;
PARSER:
m/(\ \[($re_host)\]\ )/gcix && do {

View File

@@ -22,6 +22,30 @@ sub parser {
m/(\ ($re_host):[0-9]{1,6})/gcix && do {
$host = $2;
};
} elsif($string =~ m/https\/1: (Timeout|Connection closed) during SSL handshake/) {
$_ = $string;
$reply = 'SSL handshake error';
} elsif($string =~ m/http(s\~|) http(s|)\/\<NOSRV\>/) {
if($string =~ m/-1\/-1\/-1\/-1\/[0-9]{1,20} (400|0) 0/) {
#This one seems like someone is doing something bad. Return code 400/0
$_ = $string;
$hostile = 1;
$reply = 'Bad request, return code 400/0';
PARSE:
m/(\ ($re_host):[0-9]{1,6})/gcix && do {
$host = $2;
};
} else {
#Other requests of this type seems to be ... not too bad
$reply = 'Not routed, but probs only random error codes'
}
} elsif($string =~ m/https\~ /) {
#Accepted as https, probably fine..
$reply = 'Routed as https';
} elsif($string =~ m/stopped \(cumulated conns/) {
#Usual service restart info
$hostile = 0;
$reply = 'haproxy restart information'
}
return { retval => 1, retmsg => $reply, hostile => $hostile, host => $host, string => $string };
}

View File

@@ -12,7 +12,8 @@ sub new {
sub islocal {
my $self = shift;
my $host = shift;
my @local_nets = ('127\.','10\.','192\.168\.','172\.((1[6-9])|(2[0-9])|(3[0-1]))\.','213\.236\.200\.(7|10)'); # array of regexes matching networks considered local and to be ignored
#SSL Labs - 64.41.200.0/24
my @local_nets = ('127\.','10\.','192\.168\.','172\.((1[6-9])|(2[0-9])|(3[0-1]))\.','64\.41\.200\.', '213.236.200.10','65.109.7.147'); # array of regexes matching networks considered local and to be ignored
my $local = 0;
foreach my $test(@local_nets) {
$local = 1 if($host =~ m/^$test/);

View File

@@ -6,6 +6,7 @@ use My::parser::config;
use My::parser::geoip;
use My::parser::localcheck;
use My::parser::block;
use My::parser::db;
sub new {
my $class = shift;
@@ -18,6 +19,8 @@ sub new {
$self->{'geoip'} = My::parser::geoip->new($self->{'config'});
$self->{'localcheck'} = My::parser::localcheck->new;
$self->{'block'} = My::parser::block->new($self->{'config'});
$self->{'db'} = My::parser::db->new($self->{'config'});
$self->{'config'}->set_db($self->{'db'});
return $self;
}
@@ -105,7 +108,6 @@ sub dyninit {
my $parser = shift;
if(my $initr = $self->init($fetcher_loaded,$parser)) {
$self->{'config'}->set('fetchers',$fetcher_needed,$fetcher_loaded);
#$self->{'config'}->{'logger'}->log("Dependency $fetcher_needed initialized"); FIXME add debug in config?
return 1;
}
return 0;

View File

@@ -15,17 +15,17 @@ sub new {
sub fetch {
my $self = shift;
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
my $dbh = $self->{'config'}->db_connect();
my $seq = $self->{'seq'} || 0;
my $retmsg;
my @toreturn;
unless($seq) {
my $seqsth = $self->{'dbh'}->prepare("SELECT seq FROM logs WHERE program = 'sshd' ORDER BY seq DESC LIMIT 1") or return { retval => 0, retmsg => DBI::errstr, error => 1 };
$seqsth->execute or return { retval => 0, retmsg => DBI::errstr, error => 1};
$seq = $seqsth->fetchrow_arrayref->[0] or return { retval => 0, retmsg => DBI::errstr, error => 1};
my $seqsth = $dbh->prepare("SELECT seq FROM logs WHERE program = 'sshd' ORDER BY seq DESC LIMIT 1") or return { retval => 0, retmsg => DBI::errstr, error => 1 };
$seqsth->execute() or return { retval => 0, retmsg => DBI::errstr, error => 1};
$seq = $seqsth->fetchrow_arrayref()->[0] or return { retval => 0, retmsg => DBI::errstr, error => 1};
}
my $sth = $self->{'dbh'}->prepare("SELECT msg,seq FROM logs WHERE program = 'sshd' AND seq > $seq") or return { retval => 1, retmsg => DBI::errstr, error => 1 };
$sth->execute or return { retval => 0, retmsg => DBI::errstr, error => 1};
my $sth = $dbh->prepare("SELECT msg,seq FROM logs WHERE program = 'sshd' AND seq > $seq") or return { retval => 1, retmsg => DBI::errstr, error => 1 };
$sth->execute() or return { retval => 0, retmsg => DBI::errstr, error => 1};
while(my $ref = $sth->fetchrow_hashref) {
my $string = $$ref{'msg'};
$seq = $$ref{'seq'};

View File

@@ -47,7 +47,7 @@ sub parser {
m/(from\ ($re_host)) /gcix && do {
$host = $2;
};
} elsif($string =~ m/(Disconnecting|Received disconnect from|Disconnected from|Connection closed by|Connection reset by) (authenticating |invalid |)(user .* |)$re_host port [0-9]{1,6}(\: Too many authentication failures|)( \[preauth\]|)/) {
} elsif($string =~ m/(Disconnecting|Received disconnect from|Disconnected from|Connection closed by|Connection reset by) (invalid |)(user .* |)$re_host port [0-9]{1,6}(\: Too many authentication failures|)( \[preauth\]|)/) {
$_ = $string;
$reply = 'Received disconnect';
$hostile = 1;
@@ -183,6 +183,22 @@ sub parser {
$reply = 'Reverse check failed';
} elsif($string =~ m/but this does not map back to the address/) {
$reply = 'Reverse map failure';
} elsif($string =~ m/Timeout before authentication for connection from/) {
$_ = $string;
$hostile = 1;
$reply = 'Timeout before authentication';
PARSER:
m/from\ ($re_host)\ to\ ($re_host),\ pid/gcix && do {
$host = $1;
};
} elsif($string =~ m/penalty\: exceeded LoginGraceTime/) {
$_ = $string;
$hostile = 1;
$reply = 'exceeded LoginGraceTime';
PARSER:
m/drop\ connection\ \#([0-9]{1,9})\ from\ \[($re_host)\]:([0-9]{1,9})\ on\ \[($re_host)\]:([0-9]{1,9})\ penalty:\ exceeded\ LoginGraceTime/gcix && do {
$host = $2;
};
} else {
$reply = 'No match for '.$string;
}

View File

@@ -18,8 +18,8 @@ sub new {
sub recent_hostile {
my $self = shift;
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
my $sth = $self->{'dbh'}->prepare("
my $dbh = $self->{'config'}->db_connect();
my $sth = $dbh->prepare("
SELECT COUNT(1)
FROM reject
WHERE time > DATE_SUB(NOW(), INTERVAL 5 MINUTE)")
@@ -48,7 +48,7 @@ sub checker {
#### Mapping between checks and column in db
my %values = ('reject' => 'ip','blocks' => 'ip', 'iso' => 'iso', 'asn' => 'asn', 'block_iso' => 'iso', 'block_asn' => 'asn');
my %keys = ('reject' => $host, 'blocks' => $host, 'iso' => $iso, 'block_iso' => $iso, 'asn' => $asn, 'block_asn' => $asn);
$self->{'dbh'} = $self->{'config'}->get_dbh unless($self->{'dbh'});
my $dbh = $self->{'config'}->db_connect();
foreach my $time(@times) {
foreach my $c(keys %checks) {
my $fromcheck = $self->check($keys{$c},$time,$values{$c},$checks{$c});
@@ -72,8 +72,9 @@ sub check {
my $retval;
my $rows;
my $msg;
my $dbh = $self->{'config'}->db_connect();
if($host && $allowed_time && $value && $table) {
my $sth = $self->{'dbh'}->prepare("
my $sth = $dbh->prepare("
SELECT COUNT(1)
FROM $table
WHERE $value = ? AND