Skip to content

Commit 8dea347

Browse files
authored
add brute-force protection for one-time password (rustdesk#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>
1 parent 0cf3e8e commit 8dea347

File tree

1 file changed

+55
-6
lines changed

1 file changed

+55
-6
lines changed

src/server/connection.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,11 +1993,6 @@ impl Connection {
19931993
constant_time_eq(&hasher2.finalize()[..], &self.lr.password[..])
19941994
}
19951995

1996-
#[inline]
1997-
fn validate_one_password(&self, password: &str) -> bool {
1998-
self.validate_password_plain(password)
1999-
}
2000-
20011996
fn validate_password_plain(&self, password: &str) -> bool {
20021997
if password.is_empty() {
20031998
return false;
@@ -2025,15 +2020,68 @@ impl Connection {
20252020
self.validate_password_plain(storage)
20262021
}
20272022

2023+
// This is coarse brute-force protection for the current temporary password value.
2024+
// We only care whether the active temporary password itself was presented correctly,
2025+
// not whether later authorization steps succeed. A successful temporary-password
2026+
// match clears this state immediately, and the counter also resets whenever the
2027+
// temporary password changes or is rotated.
2028+
fn check_update_temporary_password(&self, temporary_password_success: bool) {
2029+
const MAX_CONSECUTIVE_FAILURES: i32 = 10;
2030+
#[derive(Default)]
2031+
struct State {
2032+
password: String,
2033+
failures: i32,
2034+
}
2035+
lazy_static::lazy_static! {
2036+
static ref TEMPORARY_PASSWORD_FAILURES: Mutex<State> =
2037+
Mutex::new(State::default());
2038+
}
2039+
2040+
if !password::temporary_enabled() {
2041+
return;
2042+
}
2043+
2044+
let mut state = TEMPORARY_PASSWORD_FAILURES.lock().unwrap();
2045+
let current_password = password::temporary_password();
2046+
if current_password.is_empty() {
2047+
return;
2048+
}
2049+
if state.password != current_password {
2050+
state.password = current_password;
2051+
state.failures = 0;
2052+
}
2053+
2054+
if temporary_password_success {
2055+
state.failures = 0;
2056+
return;
2057+
}
2058+
state.failures += 1;
2059+
2060+
if state.failures < MAX_CONSECUTIVE_FAILURES {
2061+
return;
2062+
}
2063+
2064+
password::update_temporary_password();
2065+
let new_password = password::temporary_password();
2066+
log::warn!(
2067+
"Temporary password rotated after too many consecutive wrong attempts: failures={}, ip={}",
2068+
state.failures,
2069+
self.ip,
2070+
);
2071+
state.password = new_password;
2072+
state.failures = 0;
2073+
}
2074+
20282075
fn validate_password(&mut self, allow_permanent_password: bool) -> bool {
20292076
if password::temporary_enabled() {
20302077
let password = password::temporary_password();
2031-
if self.validate_one_password(&password) {
2078+
if self.validate_password_plain(&password) {
20322079
raii::AuthedConnID::update_or_insert_session(
20332080
self.session_key(),
20342081
Some(password),
20352082
Some(false),
20362083
);
2084+
self.check_update_temporary_password(true);
20372085
return true;
20382086
}
20392087
}
@@ -2406,6 +2454,7 @@ impl Connection {
24062454
}
24072455
if !self.validate_password(allow_logon_screen_password) {
24082456
self.update_failure(failure, false, 0);
2457+
self.check_update_temporary_password(false);
24092458
if err_msg.is_empty() {
24102459
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
24112460
.await;

0 commit comments

Comments
 (0)