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:
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)]