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

This commit is contained in:
Christophe Maudoux 2018-12-12 23:51:33 +01:00
parent caa408b424
commit aa45cf148a
5 changed files with 149 additions and 56 deletions

View File

@ -15,31 +15,32 @@ sub defaultValues {
'type' => 'category' 'type' => 'category'
} }
}, },
'authChoiceParam' => 'lmAuth', 'authChoiceParam' => 'lmAuth',
'authentication' => 'Demo', 'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Ext2F,Yubikey', 'available2F' => 'UTOTP,TOTP,U2F,REST,Ext2F,Yubikey',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey', 'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionMaxAge' => 300, 'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionTempo' => 30, 'bruteForceProtectionMaxFailed' => 3,
'captcha_mail_enabled' => 1, 'bruteForceProtectionTempo' => 30,
'captcha_register_enabled' => 1, 'captcha_mail_enabled' => 1,
'captcha_size' => 6, 'captcha_register_enabled' => 1,
'casAccessControlPolicy' => 'none', 'captcha_size' => 6,
'casAuthnLevel' => 1, 'casAccessControlPolicy' => 'none',
'checkTime' => 600, 'casAuthnLevel' => 1,
'checkXSS' => 1, 'checkTime' => 600,
'confirmFormMethod' => 'post', 'checkXSS' => 1,
'cookieName' => 'lemonldap', 'confirmFormMethod' => 'post',
'cspConnect' => '\'self\'', 'cookieName' => 'lemonldap',
'cspDefault' => '\'self\'', 'cspConnect' => '\'self\'',
'cspFont' => '\'self\'', 'cspDefault' => '\'self\'',
'cspFormAction' => '\'self\'', 'cspFont' => '\'self\'',
'cspImg' => '\'self\' data:', 'cspFormAction' => '\'self\'',
'cspScript' => '\'self\'', 'cspImg' => '\'self\' data:',
'cspStyle' => '\'self\'', 'cspScript' => '\'self\'',
'dbiAuthnLevel' => 2, 'cspStyle' => '\'self\'',
'dbiExportedVars' => {}, 'dbiAuthnLevel' => 2,
'demoExportedVars' => { 'dbiExportedVars' => {},
'demoExportedVars' => {
'cn' => 'cn', 'cn' => 'cn',
'mail' => 'mail', 'mail' => 'mail',
'uid' => 'uid' 'uid' => 'uid'

View File

@ -615,6 +615,10 @@ sub attributes {
'default' => 300, 'default' => 300,
'type' => 'int' 'type' => 'int'
}, },
'bruteForceProtectionMaxFailed' => {
'default' => 3,
'type' => 'int'
},
'bruteForceProtectionTempo' => { 'bruteForceProtectionTempo' => {
'default' => 30, 'default' => 30,
'type' => 'int' 'type' => 'int'

View File

@ -612,7 +612,13 @@ sub attributes {
default => 300, default => 300,
type => 'int', type => 'int',
documentation => 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 => { grantSessionRules => {
type => 'grantContainer', type => 'grantContainer',

View File

@ -27,7 +27,7 @@ sub init {
sub run { sub run {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $MaxAge = 0; my $MaxAge = $self->conf->{bruteForceProtectionMaxAge} + 1;
my $countFailed = 0; my $countFailed = 0;
my @lastFailedLoginEpoch = (); my @lastFailedLoginEpoch = ();
@ -37,32 +37,39 @@ sub run {
} }
$self->logger->debug(" Number of failedLogin = $countFailed"); $self->logger->debug(" Number of failedLogin = $countFailed");
return PE_OK if ( $countFailed < 3 ); return PE_OK
if ( $countFailed <= $self->conf->{bruteForceProtectionMaxFailed} );
foreach ( 0 .. 2 ) { foreach ( 0 .. $self->conf->{bruteForceProtectionMaxFailed} - 1 ) {
if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] ) { if ( defined $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_] )
{
push @lastFailedLoginEpoch, push @lastFailedLoginEpoch,
$req->sessionInfo->{_loginHistory}->{failedLogin}->[$_]->{_utime}; $req->sessionInfo->{_loginHistory}->{failedLogin}->[$_]
->{_utime};
} }
} }
$self->logger->debug("BruteForceProtection enabled"); $self->logger->debug("BruteForceProtection enabled");
# If Auth_N-2 older than MaxAge -> another try allowed # If Auth_N-MaxFailed older than MaxAge -> another try allowed
$MaxAge = $lastFailedLoginEpoch[0] - $lastFailedLoginEpoch[2]; $MaxAge
= $lastFailedLoginEpoch[0]
- $lastFailedLoginEpoch[ $self->conf->{bruteForceProtectionMaxFailed} - 1 ]
if $self->conf->{bruteForceProtectionMaxFailed};
$self->logger->debug(" -> MaxAge = $MaxAge"); $self->logger->debug(" -> MaxAge = $MaxAge");
return PE_OK 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 # 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"); $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 return PE_OK
unless ( $delta <= $self->conf->{bruteForceProtectionTempo} ); unless ( $delta <= $self->conf->{bruteForceProtectionTempo} );
# Account locked # Account locked
#shift @{ $req->sessionInfo->{_loginHistory}->{failedLogin} };
return PE_WAIT; return PE_WAIT;
} }

View File

@ -18,6 +18,9 @@ my $client = LLNG::Manager::Test->new(
loginHistoryEnabled => 1, loginHistoryEnabled => 1,
bruteForceProtection => 1, bruteForceProtection => 1,
bruteForceProtectionTempo => 5, bruteForceProtectionTempo => 5,
bruteForceProtectionMaxFailed => 4,
failedLoginNumber => 6,
successLoginNumber => 4,
} }
} }
); );
@ -30,7 +33,7 @@ ok(
length => 23, length => 23,
accept => 'text/html', accept => 'text/html',
), ),
'Auth query' '1st Auth query'
); );
count(1); count(1);
my $id1 = expectCookie($res); my $id1 = expectCookie($res);
@ -46,7 +49,55 @@ ok(
length => 23, length => 23,
accept => 'text/html', 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); count(1);
$id1 = expectCookie($res); $id1 = expectCookie($res);
@ -61,7 +112,7 @@ ok(
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
), ),
'Auth query' '1st Bad Auth query'
); );
count(1); count(1);
expectReject($res); expectReject($res);
@ -73,12 +124,36 @@ ok(
IO::String->new('user=dwho&password=ohwd'), IO::String->new('user=dwho&password=ohwd'),
length => 23 length => 23
), ),
'Auth query' '2nd Bad Auth query'
); );
count(1); count(1);
expectReject($res); expectReject($res);
## Third failed connection -> rejected ## 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);
## Fifth failed connection -> rejected
ok( ok(
$res = $client->_post( $res = $client->_post(
'/', '/',
@ -86,15 +161,15 @@ ok(
length => 23, length => 23,
accept => 'text/html', accept => 'text/html',
), ),
'Auth query' '5th Bad Auth query'
); );
count(1); 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); count(1);
sleep 1; sleep 1;
## Fourth failed connection -> Rejected ## Sixth failed connection -> Rejected
ok( ok(
$res = $client->_post( $res = $client->_post(
'/', '/',
@ -102,15 +177,15 @@ ok(
length => 23, length => 23,
accept => 'text/html', accept => 'text/html',
), ),
'Auth query' '6th Bad Auth query'
); );
count(1); 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); count(1);
sleep 2; sleep 2;
## Third successful connection -> Rejected ## Sixth successful connection -> Rejected
ok( ok(
$res = $client->_post( $res = $client->_post(
'/', '/',
@ -118,15 +193,15 @@ ok(
length => 23, length => 23,
accept => 'text/html', accept => 'text/html',
), ),
'Auth query' '6th Auth query'
); );
count(1); 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); count(1);
sleep 3; sleep 3;
## Fourth successful connection -> Accepted ## Seventh successful connection -> Accepted
ok( ok(
$res = $client->_post( $res = $client->_post(
'/', '/',
@ -134,7 +209,7 @@ ok(
length => 37, length => 37,
accept => 'text/html', accept => 'text/html',
), ),
'Auth query' '7th Auth query'
); );
count(1); count(1);
$id1 = expectCookie($res); $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 @c = ( $res->[2]->[0] =~ /<td>127.0.0.1/gs );
my @cf = ( $res->[2]->[0] =~ /PE5<\/td>/gs ); my @cf = ( $res->[2]->[0] =~ /PE5<\/td>/gs );
# History with 5 entries # History with 10 entries
ok( @c == 7, ' -> Seven entries found' ); ok( @c == 10, ' -> Ten entries found' );
ok( @cf == 4, " -> Four 'failedLogin' entries found" ); ok( @cf == 6, " -> Six 'failedLogin' entries found" );
count(3); count(3);
$client->logout($id1); $client->logout($id1);