2018-09-23 11:09:04 +02:00
|
|
|
package Lemonldap::NG::Portal::Plugins::BruteForceProtection;
|
2018-09-22 19:24:16 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
2020-10-09 22:26:00 +02:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_OK
|
|
|
|
PE_WAIT
|
|
|
|
);
|
2018-09-22 19:24:16 +02:00
|
|
|
|
2020-10-09 22:26:00 +02:00
|
|
|
our $VERSION = '2.0.10';
|
2018-09-22 19:24:16 +02:00
|
|
|
|
2018-09-22 22:25:57 +02:00
|
|
|
extends 'Lemonldap::NG::Portal::Main::Plugin';
|
2018-09-22 19:24:16 +02:00
|
|
|
|
|
|
|
# INITIALIZATION
|
2020-12-14 22:53:27 +01:00
|
|
|
use constant aroundSub => { authenticate => 'check' };
|
2018-09-22 19:24:16 +02:00
|
|
|
|
2020-02-24 21:27:50 +01:00
|
|
|
has lockTimes => (
|
|
|
|
is => 'rw',
|
|
|
|
isa => 'ArrayRef',
|
|
|
|
default => sub { [] }
|
|
|
|
);
|
|
|
|
|
|
|
|
has maxAge => (
|
|
|
|
is => 'rw',
|
|
|
|
isa => 'Int'
|
|
|
|
);
|
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
has maxFailed => (
|
|
|
|
is => 'rw',
|
|
|
|
isa => 'Int'
|
|
|
|
);
|
|
|
|
|
2018-10-09 22:54:04 +02:00
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
2020-12-14 22:53:27 +01:00
|
|
|
$self->maxFailed( abs $self->conf->{bruteForceProtectionMaxFailed} );
|
|
|
|
|
2020-01-14 19:01:37 +01:00
|
|
|
if ( $self->conf->{disablePersistentStorage} ) {
|
|
|
|
$self->logger->error(
|
|
|
|
'"BruteForceProtection" plugin enabled WITHOUT persistent session storage"'
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
2020-10-09 22:26:00 +02:00
|
|
|
|
2018-10-09 22:54:04 +02:00
|
|
|
unless ( $self->conf->{loginHistoryEnabled} ) {
|
2018-10-10 23:12:38 +02:00
|
|
|
$self->logger->error(
|
2020-01-14 19:01:37 +01:00
|
|
|
'"BruteForceProtection" plugin enabled WITHOUT "History" plugin');
|
2018-10-10 23:12:38 +02:00
|
|
|
return 0;
|
2018-10-09 22:54:04 +02:00
|
|
|
}
|
2020-10-09 22:26:00 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
unless ( $self->conf->{failedLoginNumber} > $self->maxFailed ) {
|
2020-08-27 14:38:11 +02:00
|
|
|
$self->logger->error( 'Number of failed logins history ('
|
2020-01-13 14:10:14 +01:00
|
|
|
. $self->conf->{failedLoginNumber}
|
2020-08-27 14:38:11 +02:00
|
|
|
. ') must be higher than allowed failed logins attempt ('
|
2020-12-14 22:53:27 +01:00
|
|
|
. $self->maxFailed
|
2020-01-13 14:10:14 +01:00
|
|
|
. ')' );
|
|
|
|
return 0;
|
|
|
|
}
|
2020-10-09 22:26:00 +02:00
|
|
|
|
2020-02-24 21:27:50 +01:00
|
|
|
if ( $self->conf->{bruteForceProtectionIncrementalTempo} ) {
|
|
|
|
my $lockTimes = @{ $self->lockTimes } =
|
|
|
|
sort { $a <=> $b }
|
2020-08-28 10:41:40 +02:00
|
|
|
map {
|
|
|
|
$_ =~ s/\D//;
|
|
|
|
$_ < $self->conf->{bruteForceProtectionMaxLockTime} ? $_ : ()
|
|
|
|
}
|
2020-02-24 21:27:50 +01:00
|
|
|
grep { /\d+/ }
|
2020-08-25 22:58:47 +02:00
|
|
|
split /\s*,\s*/, $self->conf->{bruteForceProtectionLockTimes};
|
2020-02-24 21:27:50 +01:00
|
|
|
|
2020-02-26 22:31:22 +01:00
|
|
|
unless ($lockTimes) {
|
2020-12-14 22:53:27 +01:00
|
|
|
@{ $self->lockTimes } = ( 15, 30, 60, 300, 600 );
|
2020-02-26 22:31:22 +01:00
|
|
|
$lockTimes = 5;
|
|
|
|
}
|
2020-05-24 00:04:33 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
for ( my $i = 1 ; $i < $self->maxFailed ; $i++ ) {
|
2020-08-27 14:38:11 +02:00
|
|
|
unshift @{ $self->lockTimes }, 0;
|
|
|
|
$lockTimes++;
|
|
|
|
}
|
2020-08-25 22:58:47 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
unless ( $lockTimes <= $self->conf->{failedLoginNumber} ) {
|
2020-08-28 14:44:03 +02:00
|
|
|
$self->logger->warn( 'Number failed logins history ('
|
2020-02-26 22:31:22 +01:00
|
|
|
. $self->conf->{failedLoginNumber}
|
2020-08-28 14:44:03 +02:00
|
|
|
. ') must be higher than incremental lock time values plus allowed failed logins attempt ('
|
|
|
|
. "$lockTimes)" );
|
2020-02-26 22:31:22 +01:00
|
|
|
splice @{ $self->lockTimes }, $self->conf->{failedLoginNumber};
|
|
|
|
$lockTimes = $self->conf->{failedLoginNumber};
|
|
|
|
}
|
2020-02-24 21:27:50 +01:00
|
|
|
|
2020-05-24 00:04:33 +02:00
|
|
|
my $sum = $self->conf->{bruteForceProtectionMaxAge} *
|
|
|
|
( 1 + $self->conf->{failedLoginNumber} - $lockTimes );
|
2020-02-24 21:27:50 +01:00
|
|
|
$sum += $_ foreach @{ $self->lockTimes };
|
|
|
|
$self->maxAge($sum);
|
|
|
|
}
|
|
|
|
else {
|
2020-12-14 22:53:27 +01:00
|
|
|
$self->maxAge( $self->conf->{bruteForceProtectionMaxAge} *
|
|
|
|
( 1 + $self->maxFailed ) );
|
2020-02-24 21:27:50 +01:00
|
|
|
}
|
2020-12-14 22:53:27 +01:00
|
|
|
|
2018-10-09 22:54:04 +02:00
|
|
|
return 1;
|
|
|
|
}
|
2018-09-22 19:24:16 +02:00
|
|
|
|
|
|
|
# RUNNING METHOD
|
2020-12-14 22:53:27 +01:00
|
|
|
sub check {
|
|
|
|
my ( $self, $sub, $req ) = @_;
|
|
|
|
my $now = time;
|
|
|
|
$self->p->setSessionInfo($req);
|
|
|
|
$self->logger->debug("Retrieve $req->{user} logins history");
|
|
|
|
$self->p->setPersistentSessionInfo( $req, $req->{user} );
|
|
|
|
|
2020-02-24 21:27:50 +01:00
|
|
|
my $countFailed = my @failedLogins =
|
2020-12-14 22:53:27 +01:00
|
|
|
map { ( $now - $_->{_utime} ) <= $self->maxAge ? $_ : () }
|
2020-02-24 21:27:50 +01:00
|
|
|
@{ $req->sessionInfo->{_loginHistory}->{failedLogin} };
|
2020-12-14 22:53:27 +01:00
|
|
|
$self->logger->debug( ' -> Failed login maxAge = ' . $self->maxAge );
|
2020-02-23 23:19:32 +01:00
|
|
|
$self->logger->debug(
|
2020-12-14 22:53:27 +01:00
|
|
|
"Number of failed login(s) to take into account = $countFailed");
|
|
|
|
my $lastFailedLoginEpoch = $failedLogins[0]->{_utime} || undef;
|
2020-02-23 23:19:32 +01:00
|
|
|
|
|
|
|
if ( $self->conf->{bruteForceProtectionIncrementalTempo} ) {
|
2020-12-14 22:53:27 +01:00
|
|
|
return $sub->($req) unless $lastFailedLoginEpoch;
|
2020-02-23 23:19:32 +01:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
# Delta between current attempt and last failed login
|
2020-02-23 23:19:32 +01:00
|
|
|
my $delta = $now - $lastFailedLoginEpoch;
|
|
|
|
$self->logger->debug(" -> Delta = $delta");
|
2020-12-14 22:53:27 +01:00
|
|
|
|
|
|
|
# Time to wait
|
2020-02-24 21:27:50 +01:00
|
|
|
my $waitingTime = $self->lockTimes->[ $countFailed - 1 ]
|
2020-08-25 22:58:47 +02:00
|
|
|
// $self->conf->{bruteForceProtectionMaxLockTime};
|
2020-12-14 22:53:27 +01:00
|
|
|
|
|
|
|
# Reach last tempo. Stop to increase waiting time
|
|
|
|
if ( $countFailed >= scalar @{ $self->lockTimes } ) {
|
|
|
|
$self->userLogger->warn(
|
|
|
|
"BruteForceProtection: Last lock time has been reached");
|
|
|
|
$self->logger->debug("Force waitingTime to last value");
|
|
|
|
$waitingTime =
|
|
|
|
$self->lockTimes->[ scalar @{ $self->lockTimes } - 1 ];
|
|
|
|
}
|
2020-02-23 23:19:32 +01:00
|
|
|
$self->logger->debug(" -> Waiting time = $waitingTime");
|
2020-12-14 22:53:27 +01:00
|
|
|
|
|
|
|
# Delta < waitingTime => wait
|
|
|
|
if ( $waitingTime && $delta < $waitingTime ) {
|
|
|
|
$self->userLogger->warn("BruteForceProtection enabled");
|
|
|
|
$req->authResult(PE_WAIT);
|
|
|
|
|
|
|
|
# Do not store failed login if last tempo or max tempo is reached
|
|
|
|
$self->p->registerLogin( $req, $req->{user} )
|
|
|
|
if ( $waitingTime < $self->conf->{bruteForceProtectionMaxLockTime}
|
|
|
|
&& $waitingTime <
|
|
|
|
$self->lockTimes->[ scalar @{ $self->lockTimes } - 1 ] );
|
|
|
|
$req->lockTime( $waitingTime - $delta );
|
2020-02-23 23:19:32 +01:00
|
|
|
return PE_WAIT;
|
|
|
|
}
|
2020-12-14 22:53:27 +01:00
|
|
|
return $sub->($req);
|
2020-02-23 23:19:32 +01:00
|
|
|
}
|
2019-07-04 22:50:46 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
return $sub->($req)
|
|
|
|
if ( $countFailed < $self->maxFailed );
|
2018-09-22 19:24:16 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
# Delta between current attempt and last failed login
|
|
|
|
my $delta = $lastFailedLoginEpoch ? $now - $lastFailedLoginEpoch : 0;
|
2018-09-28 20:08:54 +02:00
|
|
|
$self->logger->debug(" -> Delta = $delta");
|
2018-09-22 19:24:16 +02:00
|
|
|
|
2020-12-14 22:53:27 +01:00
|
|
|
# Delta < Tempo => wait
|
|
|
|
return $sub->($req)
|
|
|
|
unless ( $delta < $self->conf->{bruteForceProtectionTempo}
|
|
|
|
&& $countFailed );
|
2018-09-28 19:50:01 +02:00
|
|
|
|
2018-09-28 00:03:46 +02:00
|
|
|
# Account locked
|
2020-12-14 22:53:27 +01:00
|
|
|
$self->userLogger->warn("BruteForceProtection enabled");
|
|
|
|
$self->logger->debug(
|
|
|
|
" -> Waiting time = $self->{conf}->{bruteForceProtectionTempo}");
|
|
|
|
$req->lockTime( $self->conf->{bruteForceProtectionTempo} - $delta );
|
2018-09-28 00:03:46 +02:00
|
|
|
return PE_WAIT;
|
2018-09-22 19:24:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|