Throttle every password-verify path (GROUP/GHOST/CERT/DROP/SASL) and key the lockout by resolved account
This commit is contained in:
parent
1ecb76affb
commit
660362a34b
7 changed files with 79 additions and 11 deletions
|
|
@ -302,7 +302,10 @@ impl Db {
|
|||
/// Seconds the caller must wait before another password attempt on `account`,
|
||||
/// or None if it is not currently throttled.
|
||||
pub fn auth_lockout(&self, account: &str) -> Option<u64> {
|
||||
let until = self.auth_fails.get(&key(account))?.locked_until?;
|
||||
// Key the throttle by the resolved account, so every grouped-nick alias of
|
||||
// one account shares a single lockout bucket (else each spelling is its own).
|
||||
let k = self.resolved_key(account).unwrap_or_else(|| key(account));
|
||||
let until = self.auth_fails.get(&k)?.locked_until?;
|
||||
let now = Instant::now();
|
||||
(until > now).then(|| (until - now).as_secs() + 1)
|
||||
}
|
||||
|
|
@ -311,7 +314,7 @@ impl Db {
|
|||
/// the counter; each failure past a few free tries grows an exponential
|
||||
/// backoff, throttling guessing without a hard lockout a griefer could abuse.
|
||||
pub fn note_auth(&mut self, account: &str, success: bool) {
|
||||
let k = key(account);
|
||||
let k = self.resolved_key(account).unwrap_or_else(|| key(account));
|
||||
if success {
|
||||
self.auth_fails.remove(&k);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -314,6 +314,9 @@ impl Engine {
|
|||
actions
|
||||
}
|
||||
AuthThen::Sasl { agent, client, account } => {
|
||||
// Feed the same brute-force throttle IDENTIFY uses: success clears it,
|
||||
// a failure grows the backoff — so SASL isn't a throttle-free door.
|
||||
self.db.note_auth(&account, ok);
|
||||
if ok {
|
||||
self.sasl_login("SASL PLAIN", &agent, &client, account)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,13 @@ impl Engine {
|
|||
}]
|
||||
}
|
||||
Some((authcid, passwd)) => match self.scram_verifier(&authcid) {
|
||||
// Refuse while throttled, so SASL PLAIN can't be used to
|
||||
// brute-force past the IDENTIFY lockout.
|
||||
Some((account, _)) if self.db.auth_lockout(&account).is_some() => {
|
||||
let mut out = mk("D", vec!["F".to_string()]);
|
||||
out.extend(self.auth_report(false, Some(&account), "SASL PLAIN", &client, Some("rate limited")));
|
||||
out
|
||||
}
|
||||
Some((account, verifier)) => vec![NetAction::DeferAuthenticate {
|
||||
verifier,
|
||||
password: passwd,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue