OpenBao Login MFA Bypasses Rate Limiting and TOTP Token Reuse
Description
OpenBao exists to provide a software solution to manage, store, and distribute sensitive data including secrets, certificates, and keys. In versions 2.3.1 and below, OpenBao's Login Multi-Factor Authentication (MFA) system allows enforcing MFA using Time-based One Time Password (TOTP). Due to normalization applied by the underlying TOTP library, codes were accepted which could contain whitespace; this whitespace could bypass internal rate limiting of the MFA method and allow reuse of existing MFA codes. This issue was fixed in version 2.3.2. To work around this, use of rate-limiting quotas can limit an attacker's ability to exploit this: https://openbao.org/api-docs/system/rate-limit-quotas/.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/openbao/openbaoGo | >= 0.1.0, < 2.3.2 | 2.3.2 |
github.com/openbao/openbaoGo | < 0.0.0-20250807113757-8340a6918f6c | 0.0.0-20250807113757-8340a6918f6c |
Affected products
1Patches
18340a6918f6cAddress login MFA TOTP reuse vulnerability (#1629)
3 files changed · +48 −8
changelog/1629.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +auth/mfa: correctly limit reuse of TOTP codes during login MFA enforcement. HCSEC-2025-19 / CVE-2025-6015. +```
vault/external_tests/identity/login_mfa_totp_test.go+21 −3 modified@@ -48,6 +48,24 @@ func doTwoPhaseLogin(t *testing.T, client *api.Client, totpCodePath, methodID, u if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" { t.Fatalf("MFA validation failed to return a ClientToken in secret: %v", secret) } + + // Redo the test, ensuring that the TOTP cannot be reused. This validates + // against HCSEC-2025-19 / CVE-2025-6015. + mfaSecret, err = client.Auth().MFALogin(context.Background(), upMethod) + if err != nil { + t.Fatalf("failed to initiate second login with userpass auth method: %v", err) + } + + secret, err = client.Auth().MFAValidate( + context.Background(), + mfaSecret, + map[string]interface{}{ + methodID: []string{totpPasscode}, + }, + ) + if err == nil || secret != nil { + t.Fatalf("MFA validation succeeded when it should fail: err=%v / secret=%v", err, secret) + } } func TestLoginMfaGenerateTOTPTestAuditIncluded(t *testing.T) { @@ -282,8 +300,8 @@ func TestLoginMfaGenerateTOTPTestAuditIncluded(t *testing.T) { if err == nil { t.Fatal("MFA succeeded with an already used passcode") } - if !strings.Contains(err.Error(), "code already used") { - t.Fatalf("got: %+v, expected: code already used", err.Error()) + if !strings.Contains(err.Error(), vault.ErrBadMFACredentials.Error()) { + t.Fatalf("got: %v, expected: %v", err, vault.ErrBadMFACredentials) } // check for reaching max failed validation requests @@ -301,7 +319,7 @@ func TestLoginMfaGenerateTOTPTestAuditIncluded(t *testing.T) { _, maxErr = userClient1.Logical().WriteWithContext(context.Background(), "sys/mfa/validate", map[string]interface{}{ "mfa_request_id": secret.Auth.MFARequirement.MFARequestID, "mfa_payload": map[string][]string{ - methodID: {fmt.Sprintf("%d", i)}, + methodID: {fmt.Sprintf("%d23456", i)}, }, }) if maxErr == nil {
vault/login_mfa.go+24 −5 modified@@ -58,6 +58,8 @@ const ( mfaLoginEnforcementPrefix = "login-mfa/enforcement/" ) +var ErrBadMFACredentials = errors.New("MFA credentials not supplied or incorrect") + type totpKey struct { Key string `json:"key"` } @@ -2329,8 +2331,19 @@ func (c *Core) validatePingID(ctx context.Context, mConfig *mfa.Config, username } func (c *Core) validateTOTP(ctx context.Context, mfaFactors *MFAFactor, entityMethodSecret *mfa.Secret, configID, entityID string, usedCodes *cache.Cache, maximumValidationAttempts uint32) error { + // In HCSEC-2025-19, HashiCorp writes: + // + // > The TOTP validation will now return a generic error if the passcode + // > was already used. + // + // While such error message is of limited utility, as multiple error paths + // yielding the same error will likely still yield timing differences and + // thus are distinguishable to a determined attacker, we attempt to follow + // the same and yield the error "MFA credentials not supplied or incorrect" + // from multiple code paths here. + if mfaFactors == nil || mfaFactors.passcode == "" { - return errors.New("MFA credentials not supplied") + return ErrBadMFACredentials } passcode := mfaFactors.passcode @@ -2339,16 +2352,22 @@ func (c *Core) validateTOTP(ctx context.Context, mfaFactors *MFAFactor, entityMe return errors.New("entity does not contain the TOTP secret") } + // Validate the passcode has the right format for this totp. + if strings.TrimSpace(passcode) != passcode || len(passcode) != int(totpSecret.Digits) { + return ErrBadMFACredentials + } + usedName := fmt.Sprintf("%s_%s", configID, passcode) _, ok := usedCodes.Get(usedName) if ok { - return fmt.Errorf("code already used; new code is available in %v seconds", totpSecret.Period) + return ErrBadMFACredentials } // The duration in which a passcode is stored in cache to enforce - // rate limit on failed totp passcode validation - passcodeTTL := time.Duration(int64(time.Second) * int64(totpSecret.Period)) + // rate limit on failed totp passcode validation. See note in + // totp/path_code.go for duration calculation. + passcodeTTL := time.Duration(int64(time.Second)*int64(totpSecret.Period) + int64(2*totpSecret.Skew)) // Enforcing rate limit per MethodID per EntityID rateLimitID := fmt.Sprintf("%s_%s", configID, entityID) @@ -2392,7 +2411,7 @@ func (c *Core) validateTOTP(ctx context.Context, mfaFactors *MFAFactor, entityMe } if !valid { - return errors.New("failed to validate TOTP passcode") + return ErrBadMFACredentials } // Take the key skew, add two for behind and in front, and multiply that by
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-rxp7-9q75-vj3pghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-55003ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-6015ghsaADVISORY
- discuss.hashicorp.com/t/hcsec-2025-19-vault-login-mfa-bypass-of-rate-limiting-and-totp-token-reuse/76038ghsax_refsource_MISCWEB
- github.com/openbao/openbao/commit/8340a6918f6c41d8f75b6c3845c376d9dc32ed19ghsax_refsource_MISCWEB
- github.com/openbao/openbao/security/advisories/GHSA-rxp7-9q75-vj3pghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.