CVE-2023-43320: Insufficient Information (NVD-CWE-noinfo)
value: An issue in Proxmox Server Solutions GmbH Proxmox VE v.5.4 thru v.8.0, Proxmox Backup Server v.1.1 thru v.3.0, and Proxmox Mail Gateway v.7.1 thru v.8.0 allows a remote authenticated attacker to escalate privileges via bypassing the two-factor authentication component.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 8.8
Published: 2023-09-27T23:15Z
Repository: https://github.com/proxmox/proxmox-rs
Language: Rust
Files changed: proxmox-tfa/src/api/mod.rs
Commit date: 2023-05-10 10:26:51 +0200
Commit message: tfa: add data for rate limiting and blocking

TfaUserData uses `#[serde(deny_unknown_fields)]`, so we add
this now, but using it will require explicitly enabling it.

If the TOTP count is high, the user should be locked out of
TOTP entirely until they use a recovery key to reset the
count.

If a user's TFA try count is too high, they should get rate
limited.

In both cases they should receive some kind of notification.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>

Diff: diff --git a/proxmox-tfa/src/api/mod.rs b/proxmox-tfa/src/api/mod.rs
index 3a0373c..ec9bea4 100644
--- a/proxmox-tfa/src/api/mod.rs
+++ b/proxmox-tfa/src/api/mod.rs
@@ -329,10 +329,19 @@ pub struct TfaUserData {
 
     /// Yubico keys for a user. NOTE: This is not directly supported currently, we just need this
     /// available for PVE, where the yubico API server configuration is part if the realm.
     #[serde(skip_serializing_if = "Vec::is_empty", default)]
     pub yubico: Vec<TfaEntry<String>>,
+
+    /// Once a user runs into a TOTP limit they get locked out of TOTP until they successfully use
+    /// a recovery key.
+    #[serde(skip_serializing_if = "bool_is_false", default)]
+    pub totp_locked: bool,
+
+    /// If a user hits too many 2nd factor failures, they get completely blocked for a while.
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub tfa_blocked_until: Option<i64>,
 }
 
 impl TfaUserData {
     /// `true` if no second factors exist
     pub fn is_empty(&self) -> bool {
@@ -922,10 +931,23 @@ pub struct TfaUserChallenges {
     ///
     /// Expired values are automatically filtered out while parsing the tfa configuration file.
     #[serde(skip_serializing_if = "Vec::is_empty", default)]
     #[serde(deserialize_with = "filter_expired_challenge")]
     webauthn_auths: Vec<WebauthnAuthChallenge>,
+
+    /// Number of consecutive TOTP failures. Too many of those will lock out a user.
+    #[serde(skip_serializing_if = "u32_is_zero", default)]
+    totp_failures: u32,
+
+    /// Number of consecutive 2nd factor failures. When the limit is reached, the user is locked
+    /// out for 12 hours.
+    #[serde(skip_serializing_if = "u32_is_zero", default)]
+    tfa_failures: u32,
+}
+
+fn u32_is_zero(n: &u32) -> bool {
+    *n == 0
 }
 
 /// Serde helper using our `FilteredVecVisitor` to filter out expired entries directly at load
 /// time.
 fn filter_expired_challenge<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>

Fixed code: 
    /// Once a user runs into a TOTP limit they get locked out of TOTP until they successfully use
    /// a recovery key.
    #[serde(skip_serializing_if = "bool_is_false", default)]
    pub totp_locked: bool,

    /// If a user hits too many 2nd factor failures, they get completely blocked for a while.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub tfa_blocked_until: Option<i64>,

    /// Number of consecutive TOTP failures. Too many of those will lock out a user.
    #[serde(skip_serializing_if = "u32_is_zero", default)]
    totp_failures: u32,

    /// Number of consecutive 2nd factor failures. When the limit is reached, the user is locked
    /// out for 12 hours.
    #[serde(skip_serializing_if = "u32_is_zero", default)]
    tfa_failures: u32,
}

fn u32_is_zero(n: &u32) -> bool {
    *n == 0