mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-09 15:48:09 +03:00
add brute-force protection for one-time password (#14682)
* add brute-force protection for temporary password Rotate the temporary password after repeated failed login attempts within one minute, and reset the failure window after successful authentication. Signed-off-by: 21pages <sunboeasy@gmail.com> * replace LazyLock with lazy_static Signed-off-by: 21pages <sunboeasy@gmail.com> * read temporary password after locking failure state Signed-off-by: 21pages <sunboeasy@gmail.com> * server: rotate temporary passwords after 10 consecutive failures Signed-off-by: 21pages <sunboeasy@gmail.com> * server: clarify temporary password failure counter comment Signed-off-by: 21pages <sunboeasy@gmail.com> --------- Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
@@ -1993,11 +1993,6 @@ impl Connection {
|
|||||||
constant_time_eq(&hasher2.finalize()[..], &self.lr.password[..])
|
constant_time_eq(&hasher2.finalize()[..], &self.lr.password[..])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn validate_one_password(&self, password: &str) -> bool {
|
|
||||||
self.validate_password_plain(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_password_plain(&self, password: &str) -> bool {
|
fn validate_password_plain(&self, password: &str) -> bool {
|
||||||
if password.is_empty() {
|
if password.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
@@ -2025,15 +2020,68 @@ impl Connection {
|
|||||||
self.validate_password_plain(storage)
|
self.validate_password_plain(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is coarse brute-force protection for the current temporary password value.
|
||||||
|
// We only care whether the active temporary password itself was presented correctly,
|
||||||
|
// not whether later authorization steps succeed. A successful temporary-password
|
||||||
|
// match clears this state immediately, and the counter also resets whenever the
|
||||||
|
// temporary password changes or is rotated.
|
||||||
|
fn check_update_temporary_password(&self, temporary_password_success: bool) {
|
||||||
|
const MAX_CONSECUTIVE_FAILURES: i32 = 10;
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
password: String,
|
||||||
|
failures: i32,
|
||||||
|
}
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref TEMPORARY_PASSWORD_FAILURES: Mutex<State> =
|
||||||
|
Mutex::new(State::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !password::temporary_enabled() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = TEMPORARY_PASSWORD_FAILURES.lock().unwrap();
|
||||||
|
let current_password = password::temporary_password();
|
||||||
|
if current_password.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if state.password != current_password {
|
||||||
|
state.password = current_password;
|
||||||
|
state.failures = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if temporary_password_success {
|
||||||
|
state.failures = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.failures += 1;
|
||||||
|
|
||||||
|
if state.failures < MAX_CONSECUTIVE_FAILURES {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
password::update_temporary_password();
|
||||||
|
let new_password = password::temporary_password();
|
||||||
|
log::warn!(
|
||||||
|
"Temporary password rotated after too many consecutive wrong attempts: failures={}, ip={}",
|
||||||
|
state.failures,
|
||||||
|
self.ip,
|
||||||
|
);
|
||||||
|
state.password = new_password;
|
||||||
|
state.failures = 0;
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_password(&mut self, allow_permanent_password: bool) -> bool {
|
fn validate_password(&mut self, allow_permanent_password: bool) -> bool {
|
||||||
if password::temporary_enabled() {
|
if password::temporary_enabled() {
|
||||||
let password = password::temporary_password();
|
let password = password::temporary_password();
|
||||||
if self.validate_one_password(&password) {
|
if self.validate_password_plain(&password) {
|
||||||
raii::AuthedConnID::update_or_insert_session(
|
raii::AuthedConnID::update_or_insert_session(
|
||||||
self.session_key(),
|
self.session_key(),
|
||||||
Some(password),
|
Some(password),
|
||||||
Some(false),
|
Some(false),
|
||||||
);
|
);
|
||||||
|
self.check_update_temporary_password(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2406,6 +2454,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
if !self.validate_password(allow_logon_screen_password) {
|
if !self.validate_password(allow_logon_screen_password) {
|
||||||
self.update_failure(failure, false, 0);
|
self.update_failure(failure, false, 0);
|
||||||
|
self.check_update_temporary_password(false);
|
||||||
if err_msg.is_empty() {
|
if err_msg.is_empty() {
|
||||||
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
Reference in New Issue
Block a user