OpenBao: Timing Side-Channel in Userpass Auth Method
Description
OpenBao exists to provide a software solution to manage, store, and distribute sensitive data including secrets, certificates, and keys. In versions 0.1.0 through 2.3.1, when using OpenBao's userpass auth method, user enumeration was possible due to timing difference between non-existent users and users with stored credentials. This is independent of whether the supplied credentials were valid for the given user. This issue was fixed in version 2.3.2. To work around this issue, users may use another auth method or apply rate limiting quotas to limit the number of requests in a period of time: 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-20250806193356-4d9b5d3d6486 | 0.0.0-20250806193356-4d9b5d3d6486 |
Affected products
1Patches
14d9b5d3d6486Fix timing leak in userpass auth method (#1628)
3 files changed · +102 −9
builtin/credential/userpass/path_login.go+17 −9 modified@@ -13,9 +13,16 @@ import ( "github.com/openbao/openbao/sdk/v2/helper/cidrutil" "github.com/openbao/openbao/sdk/v2/helper/policyutil" "github.com/openbao/openbao/sdk/v2/logical" + "github.com/openbao/openbao/sdk/v2/physical" "golang.org/x/crypto/bcrypt" ) +// bcrypt.GenerateFromPassword is impossible to error, assuming we: +// - provide password of a appriopriate length (<72) +// - provide valid cost +// both criteria we meet +var dummyHash, _ = bcrypt.GenerateFromPassword([]byte("dummy"), bcrypt.DefaultCost) + func pathLogin(b *backend) *framework.Path { return &framework.Path{ Pattern: "login/" + framework.GenericNameRegex("username"), @@ -74,24 +81,25 @@ func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framew return nil, errors.New("missing password") } + // bypass cache as we want to hit the storage + getUserCtx := physical.CacheRefreshContext(ctx, false) // Get the user and validate auth - user, userError := b.user(ctx, req.Storage, username) + user, userError := b.user(getUserCtx, req.Storage, username) var userPassword []byte - // If there was an error or it's nil, we fake a password for the bcrypt - // check so as not to have a timing leak. Specifics of the underlying - // storage still leaks a bit but generally much more in the noise compared - // to bcrypt. + // If there was an error or it's nil, we fake a password hash + // for the bcrypt check so as not to have a timing leak. if user != nil && userError == nil { if len(user.PasswordHash) == 0 { - return nil, errors.New("invalid user entry: refusing to process pre-Vault v0.2 record") + return logical.ErrorResponse("invalid username or password"), nil } userPassword = user.PasswordHash } else { - // This is still acceptable as bcrypt will still make sure it takes - // a long time, it's just nicer to be random if possible - userPassword = []byte("dummy") + // This is still acceptable as bcrypt will still make sure + // it takes comparable amount of time, assuming the hash + // meets the criteria set before hash comparison + userPassword = dummyHash } // Check for a password match.
builtin/credential/userpass/path_login_test.go+82 −0 added@@ -0,0 +1,82 @@ +// Copyright (c) 2025 OpenBao a Series of LF Projects, LLC +// SPDX-License-Identifier: MPL-2.0 + +package userpass + +import ( + "context" + "testing" + "time" + + "github.com/armon/go-metrics" + log "github.com/hashicorp/go-hclog" + "github.com/openbao/openbao/helper/namespace" + "github.com/openbao/openbao/sdk/v2/helper/logging" + "github.com/openbao/openbao/sdk/v2/logical" + "github.com/openbao/openbao/sdk/v2/physical" + "github.com/openbao/openbao/sdk/v2/physical/inmem" + "github.com/stretchr/testify/require" +) + +func TestPathLogin_TimingLeak(t *testing.T) { + logger := logging.NewVaultLogger(log.Trace) + inm, err := inmem.NewInmem(nil, logger) + require.NoError(t, err) + + latency := physical.NewLatencyInjector(inm, 2*time.Second, 1, logger) + cache := physical.NewCache(latency, 0, logger, &metrics.BlackholeSink{}) + storage := logical.NewLogicalStorage(cache) + config := logical.TestBackendConfig() + config.StorageView = storage + + ctx := namespace.RootContext(context.Background()) + b, err := Factory(ctx, config) + require.NoError(t, err) + require.NotNil(t, b) + + req := &logical.Request{ + Operation: logical.UpdateOperation, + Path: "users/test", + Storage: storage, + Data: map[string]interface{}{ + "password": "password", + "policies": "foo", + }, + } + + // Create user + _, err = b.HandleRequest(ctx, req) + require.NoError(t, err) + + req = &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login/test", + Storage: storage, + Data: map[string]interface{}{ + "password": "invalid-password", + }, + } + + start := time.Now() + resp, err := b.HandleRequest(ctx, req) + // ensuring we actually hit the storage + require.Greater(t, time.Since(start).Seconds(), 2.01*time.Second.Seconds()) + require.Equal(t, resp.Data["error"], "invalid username or password") + require.ErrorIs(t, err, logical.ErrInvalidCredentials) + + req = &logical.Request{ + Operation: logical.UpdateOperation, + Path: "login/non-existing", + Storage: storage, + Data: map[string]interface{}{ + "password": "invalid-password", + }, + } + + start = time.Now() + resp, err = b.HandleRequest(ctx, req) + // ensuring we actually hit the storage + require.Greater(t, time.Since(start).Seconds(), 2.01*time.Second.Seconds()) + require.Equal(t, resp.Data["error"], "invalid username or password") + require.Nil(t, err) +}
changelog/1628.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +auth/userpass: Prevent timing-based leak in userpass auth method. HCSEC-2025-15 / CVE-2025-6011. +``` \ No newline at end of file
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
7- github.com/advisories/GHSA-hh28-h22f-8357ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54999ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-6011ghsaADVISORY
- discuss.hashicorp.com/t/hcsec-2025-15-timing-side-channel-in-vault-s-userpass-auth-method/76034ghsax_refsource_MISCWEB
- discuss.hashicorp.com/t/hcsec-2025-21-vault-user-enumeration-in-userpass-auth-method/76095ghsax_refsource_MISCWEB
- github.com/openbao/openbao/commit/4d9b5d3d6486ab9fbd5b644173fa0097015d6626ghsax_refsource_MISCWEB
- github.com/openbao/openbao/security/advisories/GHSA-hh28-h22f-8357ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.