vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 88bea44dd81c6fc9755d42d9bee2533db8765c2a
parent 8ee5d51bd47279d5b23c409744fab6614af0e918
Author: Jeremy Lin <jeremy.lin@gmail.com>
Date:   Sat, 10 Jul 2021 01:21:27 -0700

Prevent user enumeration via password hints

When `show_password_hint` is enabled but mail is not configured, the previous
implementation returned a differentiable response for non-existent email
addresses.

Even if mail is enabled, there is a timing side channel since mail is sent
synchronously. Add a randomized sleep to mitigate this somewhat.

Diffstat:
Msrc/api/core/accounts.rs | 49+++++++++++++++++++++++++++++++++++--------------
1 file changed, 35 insertions(+), 14 deletions(-)

diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -576,24 +576,45 @@ struct PasswordHintData { #[post("/accounts/password-hint", data = "<data>")] fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { - let data: PasswordHintData = data.into_inner().data; + if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() { + err!("This server is not configured to provide password hints."); + } - let hint = match User::find_by_mail(&data.Email, &conn) { - Some(user) => user.password_hint, - None => return Ok(()), - }; + const NO_HINT: &str = "Sorry, you have no password hint..."; - if CONFIG.mail_enabled() { - mail::send_password_hint(&data.Email, hint)?; - } else if CONFIG.show_password_hint() { - if let Some(hint) = hint { - err!(format!("Your password hint is: {}", &hint)); - } else { - err!("Sorry, you have no password hint..."); + let data: PasswordHintData = data.into_inner().data; + let email = &data.Email; + + match User::find_by_mail(email, &conn) { + None => { + // To prevent user enumeration, act as if the user exists. + if CONFIG.mail_enabled() { + // There is still a timing side channel here in that the code + // paths that send mail take noticeably longer than ones that + // don't. Add a randomized sleep to mitigate this somewhat. + use rand::{thread_rng, Rng}; + let mut rng = thread_rng(); + let base = 1000; + let delta: i32 = 100; + let sleep_ms = (base + rng.gen_range(-delta..=delta)) as u64; + std::thread::sleep(std::time::Duration::from_millis(sleep_ms)); + Ok(()) + } else { + err!(NO_HINT); + } + } + Some(user) => { + let hint: Option<String> = user.password_hint; + if CONFIG.mail_enabled() { + mail::send_password_hint(email, hint)?; + Ok(()) + } else if let Some(hint) = hint { + err!(format!("Your password hint is: {}", hint)); + } else { + err!(NO_HINT); + } } } - - Ok(()) } #[derive(Deserialize)]