Commit aa45cf14 authored by Christophe Maudoux's avatar Christophe Maudoux 🐛
Browse files

Append bruteForce Protection number of allowed failed Login parameter (#1506)

parent caa408b4
......@@ -15,31 +15,32 @@ sub defaultValues {
'type' => 'category'
}
},
'authChoiceParam' => 'lmAuth',
'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Ext2F,Yubikey',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionTempo' => 30,
'captcha_mail_enabled' => 1,
'captcha_register_enabled' => 1,
'captcha_size' => 6,
'casAccessControlPolicy' => 'none',
'casAuthnLevel' => 1,
'checkTime' => 600,
'checkXSS' => 1,
'confirmFormMethod' => 'post',
'cookieName' => 'lemonldap',
'cspConnect' => '\'self\'',
'cspDefault' => '\'self\'',
'cspFont' => '\'self\'',
'cspFormAction' => '\'self\'',
'cspImg' => '\'self\' data:',
'cspScript' => '\'self\'',
'cspStyle' => '\'self\'',
'dbiAuthnLevel' => 2,
'dbiExportedVars' => {},
'demoExportedVars' => {
'authChoiceParam' => 'lmAuth',
'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Ext2F,Yubikey',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionMaxFailed' => 3,
'bruteForceProtectionTempo' => 30,
'captcha_mail_enabled' => 1,
'captcha_register_enabled' => 1,
'captcha_size' => 6,
'casAccessControlPolicy' => 'none',
'casAuthnLevel' => 1,
'checkTime' => 600,
'checkXSS' => 1,
'confirmFormMethod' => 'post',
'cookieName' => 'lemonldap',
'cspConnect' => '\'self\'',
'cspDefault' => '\'self\'',
'cspFont' => '\'self\'',
'cspFormAction' => '\'self\'',
'cspImg' => '\'self\' data:',
'cspScript' => '\'self\'',
'cspStyle' => '\'self\'',
'dbiAuthnLevel' => 2,
'dbiExportedVars' => {},
'demoExportedVars' => {
'cn' => 'cn',
'mail' => 'mail',
'uid' => 'uid'
......
......@@ -615,6 +615,10 @@ sub attributes {
'default' => 300,
'type' => 'int'
},
'bruteForceProtectionMaxFailed' => {
'default' => 3,
'type' => 'int'
},
'bruteForceProtectionTempo' => {
'default' => 30,
'type' => 'int'
......
......@@ -612,7 +612,13 @@ sub attributes {
default => 300,
type => 'int',
documentation =>
'Brute force attack protection -> Max age third failed login',
'Brute force attack protection -> Max age between last and first allowed failed login',
},
bruteForceProtectionMaxFailed => {
default => 3,
type => 'int',
documentation =>
'Brute force attack protection -> Max allowed failed login',
},
grantSessionRules => {
type => 'grantContainer',
......
......@@ -27,7 +27,7 @@ sub init {
sub run {
my ( $self, $req ) = @_;
my $MaxAge = 0;
my $MaxAge = $self->conf->{bruteForceProtectionMaxAge} + 1;
my $countFailed = 0;
my @lastFailedLoginEpoch = ();
......@@ -37,32 +37,39 @@ sub run {
}
$self->logger->debug(" Number of failedLogin = $countFailed");
return PE_OK if ( $countFailed < 3 );
return PE_OK
if ( $countFailed <= $self->conf->{bruteForceProtectionMaxFailed} );
foreach ( 0 .. 2 ) {
if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] ) {
foreach ( 0 .. $self->conf->{bruteForceProtectionMaxFailed} - 1 ) {
if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] )
{
push @lastFailedLoginEpoch,
$req->sessionInfo->{_loginHistory}->{failedLogin}->[$_]->{_utime};
$req->sessionInfo->{_loginHistory}->{failedLogin}->[$_]
->{_utime};
}
}
$self->logger->debug("BruteForceProtection enabled");
# If Auth_N-2 older than MaxAge -> another try allowed
$MaxAge = $lastFailedLoginEpoch[0] - $lastFailedLoginEpoch[2];
# If Auth_N-MaxFailed older than MaxAge -> another try allowed
$MaxAge
= $lastFailedLoginEpoch[0]
- $lastFailedLoginEpoch[ $self->conf->{bruteForceProtectionMaxFailed} - 1 ]
if $self->conf->{bruteForceProtectionMaxFailed};
$self->logger->debug(" -> MaxAge = $MaxAge");
return PE_OK
if ( $MaxAge > $self->conf->{bruteForceProtectionMaxAge} );
if ( $MaxAge > $self->conf->{bruteForceProtectionMaxAge} );
# Delta between the two last failed logins -> Auth_N - Auth_N-1
my $delta = time - $lastFailedLoginEpoch[1];
my $delta = 0;
$delta = time - $lastFailedLoginEpoch[1]
if defined $lastFailedLoginEpoch[1];
$self->logger->debug(" -> Delta = $delta");
# Delta between the two last failed logins < 30s => wait
# Delta between the two last failed logins < Tempo => wait
return PE_OK
unless ( $delta <= $self->conf->{bruteForceProtectionTempo} );
unless ( $delta <= $self->conf->{bruteForceProtectionTempo} );
# Account locked
#shift @{ $req->sessionInfo->{_loginHistory}->{failedLogin} };
return PE_WAIT;
}
......
......@@ -18,6 +18,9 @@ my $client = LLNG::Manager::Test->new(
loginHistoryEnabled => 1,
bruteForceProtection => 1,
bruteForceProtectionTempo => 5,
bruteForceProtectionMaxFailed => 4,
failedLoginNumber => 6,
successLoginNumber => 4,
}
}
);
......@@ -30,7 +33,7 @@ ok(
length => 23,
accept => 'text/html',
),
'Auth query'
'1st Auth query'
);
count(1);
my $id1 = expectCookie($res);
......@@ -46,7 +49,55 @@ ok(
length => 23,
accept => 'text/html',
),
'Auth query'
'2nd Auth query'
);
count(1);
$id1 = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id1);
## Third successful connection
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'3rd Auth query'
);
count(1);
$id1 = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id1);
## Forth successful connection
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'4th Auth query'
);
count(1);
$id1 = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id1);
## Fifth successful connection
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
accept => 'text/html',
),
'5th Auth query'
);
count(1);
$id1 = expectCookie($res);
......@@ -61,7 +112,7 @@ ok(
IO::String->new('user=dwho&password=ohwd'),
length => 23
),
'Auth query'
'1st Bad Auth query'
);
count(1);
expectReject($res);
......@@ -73,12 +124,36 @@ ok(
IO::String->new('user=dwho&password=ohwd'),
length => 23
),
'Auth query'
'2nd Bad Auth query'
);
count(1);
expectReject($res);
## Third failed connection
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=ohwd'),
length => 23
),
'3rd Bad Auth query'
);
count(1);
expectReject($res);
## Forth failed connection
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=ohwd'),
length => 23
),
'4th Bad Auth query'
);
count(1);
expectReject($res);
## Third failed connection -> rejected
## Fifth failed connection -> rejected
ok(
$res = $client->_post(
'/',
......@@ -86,15 +161,15 @@ ok(
length => 23,
accept => 'text/html',
),
'Auth query'
'5th Bad Auth query'
);
count(1);
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Protection enabled' );
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Rejected -> Protection enabled' );
count(1);
sleep 1;
## Fourth failed connection -> Rejected
## Sixth failed connection -> Rejected
ok(
$res = $client->_post(
'/',
......@@ -102,15 +177,15 @@ ok(
length => 23,
accept => 'text/html',
),
'Auth query'
'6th Bad Auth query'
);
count(1);
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Protection enabled' );
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Rejected -> Protection enabled' );
count(1);
sleep 2;
## Third successful connection -> Rejected
## Sixth successful connection -> Rejected
ok(
$res = $client->_post(
'/',
......@@ -118,15 +193,15 @@ ok(
length => 23,
accept => 'text/html',
),
'Auth query'
'6th Auth query'
);
count(1);
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Protection enabled' );
ok( $res->[2]->[0] =~ /<span trmsg="86"><\/span>/, 'Rejected -> Protection enabled' );
count(1);
sleep 3;
## Fourth successful connection -> Accepted
## Seventh successful connection -> Accepted
ok(
$res = $client->_post(
'/',
......@@ -134,7 +209,7 @@ ok(
length => 37,
accept => 'text/html',
),
'Auth query'
'7th Auth query'
);
count(1);
$id1 = expectCookie($res);
......@@ -145,9 +220,9 @@ ok( $res->[2]->[0] =~ /trspan="lastLogins"/, 'History found' )
my @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
my @cf = ( $res->[2]->[0] =~ /PE5<\/td>/gs );
# History with 5 entries
ok( @c == 7, ' -> Seven entries found' );
ok( @cf == 4, " -> Four 'failedLogin' entries found" );
# History with 10 entries
ok( @c == 10, ' -> Ten entries found' );
ok( @cf == 6, " -> Six 'failedLogin' entries found" );
count(3);
$client->logout($id1);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment