From 1e69a241b22d26efd3a9d9ca37fd7cefab1a173c Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov <ivadmi5@gmail.com> Date: Thu, 30 Nov 2023 19:21:38 +0300 Subject: [PATCH] fix(auth): check if user exists in auth attempts tracker This patch fixes a possible DOS by checking whether the user exists before creating a record for its unsuccessful auth attempt. This is due to the fact that `box.session.on_auth` will be called even for unknown users (this helps vanilla users log such events). --- src/lib.rs | 22 ++++++++++++++++++++++ test/int/test_acl.py | 8 ++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3d1ddbe441..e312d5440d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -292,6 +292,7 @@ fn set_login_attempts_check(storage: Clusterwide) { enum Verdict { AuthOk, AuthFail, + UnknownUser, UserBlocked, } @@ -315,6 +316,19 @@ fn set_login_attempts_check(storage: Clusterwide) { .expect("accessing storage should not fail") }; + let user_exists = storage + .users + .by_name(&user) + .expect("accessing storage should not fail") + .is_some(); + + // Prevent DOS attacks by first checking whether the user exists. + // If it doesn't, we shouldn't even bother tracking its attempts. + // Too many hashmap records will cause a global OOM event. + if !user_exists { + return Verdict::UnknownUser; + } + match attempts.entry(user) { Entry::Occupied(e) if *e.get() >= max_login_attempts() => { // The account is suspended until restart @@ -365,6 +379,14 @@ fn set_login_attempts_check(storage: Clusterwide) { user: &user, ); } + Verdict::UnknownUser => { + crate::audit!( + message: "failed to authenticate unknown user `{user}`", + title: "auth_fail", + severity: High, + user: &user, + ); + } Verdict::UserBlocked => { crate::audit!( message: "failed to authenticate user `{user}`", diff --git a/test/int/test_acl.py b/test/int/test_acl.py index 88db16da3c..89515fe320 100644 --- a/test/int/test_acl.py +++ b/test/int/test_acl.py @@ -47,14 +47,14 @@ def test_max_login_attempts(cluster: Cluster): c.eval( """ box.session.su(1) - box.schema.user.create('foo', {password='bar'}) - box.schema.user.grant('foo', 'read,write,execute', 'universe') + pico.create_user('foo', '12345678') + pico.grant_privilege('foo', 'execute', 'universe') """ ) c.close() # First login is successful - c = connect(i1, user="foo", password="bar") + c = connect(i1, user="foo", password="12345678") assert c # Several failed login attempts but one less than maximum @@ -69,7 +69,7 @@ def test_max_login_attempts(cluster: Cluster): c = connect( i1, user="foo", - password="bar", + password="12345678", ) assert c -- GitLab