VYPR
Moderate severityNVD Advisory· Published Aug 9, 2025· Updated Aug 11, 2025

OpenBao Login MFA Bypasses Rate Limiting and TOTP Token Reuse

CVE-2025-55003

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.

PackageAffected versionsPatched versions
github.com/openbao/openbaoGo
>= 0.1.0, < 2.3.22.3.2
github.com/openbao/openbaoGo
< 0.0.0-20250807113757-8340a6918f6c0.0.0-20250807113757-8340a6918f6c

Affected products

1

Patches

1
8340a6918f6c

Address login MFA TOTP reuse vulnerability (#1629)

https://github.com/openbao/openbaoAlexander ScheelAug 7, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.