HashiCorp Vault improper configuration of multi factor authentication in github.com/hashicorp/vault
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
1.10.0 → fixed in 1.10.3
Details
HashiCorp Vault improper configuration of multi factor authentication in github.com/hashicorp/vault
The fix
loading MFA configs upont restart (#15261) (#15308)
changelog/15261.txt+3 −0
@@ -0,0 +1,3 @@+```release-note:bug+auth: load login MFA configuration upon restart+```
vault/core.go+36 −7
@@ -35,6 +35,7 @@ import ("github.com/hashicorp/vault/api""github.com/hashicorp/vault/audit""github.com/hashicorp/vault/command/server"+"github.com/hashicorp/vault/helper/identity/mfa""github.com/hashicorp/vault/helper/metricsutil""github.com/hashicorp/vault/helper/namespace""github.com/hashicorp/vault/physical/raft"@@ -2113,7 +2114,6 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, cif err := c.setupQuotas(ctx, false); err != nil {return err}-c.setupCachedMFAResponseAuth()if err := c.setupHeaderHMACKey(ctx, false); err != nil {return err@@ -2135,9 +2135,14 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, cif err := c.loadIdentityStoreArtifacts(ctx); err != nil {return err}-if err := loadMFAConfigs(ctx, c); err != nil {+if err := loadPolicyMFAConfigs(ctx, c); err != nil {return err}+c.setupCachedMFAResponseAuth()+if err := c.loadLoginMFAConfigs(ctx); err != nil {+return err+}+if err := c.setupAuditedHeadersConfig(ctx); err != nil {return err}@@ -2299,10 +2304,6 @@ func (c *Core) preSeal() error {result = multierror.Append(result, fmt.Errorf("error stopping expiration: %w", err))}c.stopActivityLog()-// Clear any cached auth response-c.mfaResponseAuthQueueLock.Lock()-c.mfaResponseAuthQueue = nil-c.mfaResponseAuthQueueLock.Unlock()if err := c.teardownCredentials(context.Background()); err != nil {result = multierror.Append(result, fmt.Errorf("error tearing down credentials: %w", err))@@ -2330,10 +2331,13 @@ func (c *Core) preSeal() error {seal.StopHealthCheck()}-c.loginMFABackend.usedCodes = nilif c.systemBackend != nil && c.systemBackend.mfaBackend != nil {c.systemBackend.mfaBackend.usedCodes = nil}+if err := c.teardownLoginMFA(); err != nil {+result = multierror.Append(result, fmt.Errorf("error tearing down login MFA, error: %w", err))+}+preSealPhysical(c)c.logger.Info("pre-seal teardown complete")@@ -3047,6 +3051,31 @@ type LicenseState struct {Terminated bool}+func (c *Core) loadLoginMFAConfigs(ctx context.Context) error {+eConfigs := make([]*mfa.MFAEnforcementConfig, 0)+allNamespaces := c.collectNamespaces()+for _, ns := range allNamespaces {+err := c.loginMFABackend.loadMFAMethodConfigs(ctx, ns)+if err != nil {+return fmt.Errorf("error loading MFA method Config, namespaceid %s, error: %w", ns.ID, err)+}++loadedConfigs, err := c.loginMFABackend.loadMFAEnforcementConfigs(ctx, ns)+if err != nil {+return fmt.Errorf("error loading MFA enforcement Config, namespaceid %s, error: %w", ns.ID, err)+}++eConfigs = append(eConfigs, loadedConfigs...)+}++for _, conf := range eConfigs {+if err := c.loginMFABackend.loginMFAMethodExistenceCheck(conf); err != nil {+c.loginMFABackend.mfaLogger.Error("failed to find all MFA methods that exist in MFA enforcement configs", "configID", conf.ID, "namespaceID", conf.NamespaceID, "error", err.Error())+}+}+return nil+}+type MFACachedAuthResponse struct {CachedAuth *logical.AuthRequestPath string
vault/core_util.go+1 −1
@@ -115,7 +115,7 @@ func postUnsealPhysical(c *Core) error {return nil}-func loadMFAConfigs(context.Context, *Core) error { return nil }+func loadPolicyMFAConfigs(context.Context, *Core) error { return nil }func shouldStartClusterListener(*Core) bool { return true }
vault/login_mfa.go+161 −30
@@ -122,6 +122,18 @@ func NewLoginMFABackend(core *Core, logger hclog.Logger) *LoginMFABackend {}func NewMFABackend(core *Core, logger hclog.Logger, prefix string, schemaFuncs []func() *memdb.TableSchema) *MFABackend {+db, _ := SetupMFAMemDB(schemaFuncs)+return &MFABackend{+Core: core,+mfaLock: &sync.RWMutex{},+db: db,+mfaLogger: logger.Named("mfa"),+namespacer: core,+methodTable: prefix,+}+}++func SetupMFAMemDB(schemaFuncs []func() *memdb.TableSchema) (*memdb.MemDB, error) {mfaSchemas := &memdb.DBSchema{Tables: make(map[string]*memdb.TableSchema),}@@ -134,15 +146,24 @@ func NewMFABackend(core *Core, logger hclog.Logger, prefix string, schemaFuncs [mfaSchemas.Tables[schema.Name] = schema}-db, _ := memdb.NewMemDB(mfaSchemas)-return &MFABackend{-Core: core,-mfaLock: &sync.RWMutex{},-db: db,-mfaLogger: logger.Named("mfa"),-namespacer: core,-methodTable: prefix,+db, err := memdb.NewMemDB(mfaSchemas)+if err != nil {+return nil, err}+return db, nil+}++func (b *LoginMFABackend) ResetLoginMFAMemDB() error {+var err error++db, err := SetupMFAMemDB(loginMFASchemaFuncs())+if err != nil {+return err+}++b.db = db++return nil}func (i *IdentityStore) handleMFAMethodListTOTP(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {@@ -474,6 +495,103 @@ func (i *IdentityStore) handleLoginMFAAdminDestroyUpdate(ctx context.Context, rereturn nil, nil}+// loadMFAMethodConfigs loads MFA method configs for login MFA+func (b *LoginMFABackend) loadMFAMethodConfigs(ctx context.Context, ns *namespace.Namespace) error {+b.mfaLogger.Trace("loading login MFA configurations")+barrierView, err := b.Core.barrierViewForNamespace(ns.ID)+if err != nil {+return fmt.Errorf("error getting namespace view, namespaceid %s, error %w", ns.ID, err)+}+existing, err := barrierView.List(ctx, loginMFAConfigPrefix)+if err != nil {+return fmt.Errorf("failed to list MFA configurations for namespace path %s and prefix %s: %w", ns.Path, loginMFAConfigPrefix, err)+}+b.mfaLogger.Trace("methods collected", "num_existing", len(existing))++for _, key := range existing {+b.mfaLogger.Trace("loading method", "method", key)++// Read the config from storage+mConfig, err := b.getMFAConfig(ctx, loginMFAConfigPrefix+key, barrierView)+if err != nil {+return err+}++if mConfig == nil {+b.mfaLogger.Trace("failed to find the config related to a method", "namespace", ns.Path, "prefix", loginMFAConfigPrefix, "method", key)+continue+}++// Load the config in MemDB+err = b.MemDBUpsertMFAConfig(ctx, mConfig)+if err != nil {+return fmt.Errorf("failed to load configuration ID %s prefix %s in MemDB: %w", mConfig.ID, loginMFAConfigPrefix, err)+}+}++b.mfaLogger.Trace("configurations restored", "namespace", ns.Path, "prefix", loginMFAConfigPrefix)++return nil+}++// loadMFAEnforcementConfigs loads MFA method configs for login MFA+func (b *LoginMFABackend) loadMFAEnforcementConfigs(ctx context.Context, ns *namespace.Namespace) ([]*mfa.MFAEnforcementConfig, error) {+b.mfaLogger.Trace("loading login MFA enforcement configurations")+barrierView, err := b.Core.barrierViewForNamespace(ns.ID)+if err != nil {+return nil, fmt.Errorf("error getting namespace view, namespaceid %s, error %w", ns.ID, err)+}+existing, err := barrierView.List(ctx, mfaLoginEnforcementPrefix)+if err != nil {+return nil, fmt.Errorf("failed to list MFA enforcement configurations for namespace %s with prefix %s: %w", ns.Path, mfaLoginEnforcementPrefix, err)+}+b.mfaLogger.Trace("enforcements configs collected", "num_existing", len(existing))++eConfigs := make([]*mfa.MFAEnforcementConfig, 0)+for _, key := range existing {+b.mfaLogger.Trace("loading enforcement", "config", key)++// Read the config from storage+mConfig, err := b.getMFALoginEnforcementConfig(ctx, mfaLoginEnforcementPrefix+key, barrierView)+if err != nil {+return nil, err+}++if mConfig == nil {+b.mfaLogger.Trace("failed to find an enforcement config", "namespace", ns.Path, "prefix", mfaLoginEnforcementPrefix, "config", key)+continue+}++// Load the config in MemDB+err = b.MemDBUpsertMFALoginEnforcementConfig(ctx, mConfig)+if err != nil {+return nil, fmt.Errorf("failed to load enforcement configuration ID %s with prefix %s in MemDB: %w", mConfig.ID, mfaLoginEnforcementPrefix, err)+}++eConfigs = append(eConfigs, mConfig)+}++b.mfaLogger.Trace("enforcement configurations restored", "namespace", ns.Path, "prefix", mfaLoginEnforcementPrefix)++return eConfigs, nil+}++func (b *LoginMFABackend) loginMFAMethodExistenceCheck(eConfig *mfa.MFAEnforcementConfig) error {+var aggErr *multierror.Error+for _, confID := range eConfig.MFAMethodIDs {+config, memErr := b.MemDBMFAConfigByID(confID)+if memErr != nil {+aggErr = multierror.Append(aggErr, memErr)+return aggErr.ErrorOrNil()+}+if config == nil {+aggErr = multierror.Append(aggErr, fmt.Errorf("found an MFA method ID in enforcement config, but failed to find the MFA method config method ID %s", confID))+}+}++return aggErr.ErrorOrNil()+}+func (b *LoginMFABackend) handleMFALoginValidate(ctx context.Context, req *logical.Request, d *framework.FieldData) (retResp *logical.Response, retErr error) {// mfaReqID is the ID of the login requestmfaReqID := d.Get("mfa_request_id").(string)@@ -551,6 +669,22 @@ func (b *LoginMFABackend) handleMFALoginValidate(ctx context.Context, req *logicreturn resp, nil}+func (c *Core) teardownLoginMFA() error {+if !c.IsDRSecondary() {+// Clear any cached auth response+c.mfaResponseAuthQueueLock.Lock()+c.mfaResponseAuthQueue = nil+c.mfaResponseAuthQueueLock.Unlock()++c.loginMFABackend.usedCodes = nil++if err := c.loginMFABackend.ResetLoginMFAMemDB(); err != nil {+return err+}+}+return nil+}+// LoginMFACreateToken creates a token after the login MFA is validated.// It also applies the lease quotas on the original login request path.func (c *Core) LoginMFACreateToken(ctx context.Context, reqPath string, cachedAuth *logical.Auth) (*logical.Response, error) {@@ -2529,6 +2663,25 @@ func (b *MFABackend) getMFAConfig(ctx context.Context, path string, barrierViewreturn &mConfig, nil}+func (b *LoginMFABackend) getMFALoginEnforcementConfig(ctx context.Context, path string, barrierView *BarrierView) (*mfa.MFAEnforcementConfig, error) {+entry, err := barrierView.Get(ctx, path)+if err != nil {+return nil, err+}++if entry == nil {+return nil, nil+}++var mConfig mfa.MFAEnforcementConfig+err = proto.Unmarshal(entry.Value, &mConfig)+if err != nil {+return nil, err+}++return &mConfig, nil+}+func (b *LoginMFABackend) putMFALoginEnforcementConfig(ctx context.Context, eConfig *mfa.MFAEnforcementConfig) error {entryIndex := mfaLoginEnforcementPrefix + eConfig.IDmarshaledEntry, err := proto.Marshal(eConfig)@@ -2547,28 +2700,6 @@ func (b *LoginMFABackend) putMFALoginEnforcementConfig(ctx context.Context, eCon})}-func (b *LoginMFABackend) getMFALoginEnforcementConfig(ctx context.Context, key, namespaceId string) (*mfa.MFAEnforcementConfig, error) {-barrierView, err := b.Core.barrierViewForNamespace(namespaceId)-if err != nil {-return nil, err-}-entry, err := barrierView.Get(ctx, mfaLoginEnforcementPrefix+key)-if err != nil {-return nil, err-}-if entry == nil {-return nil, nil-}--var eConfig mfa.MFAEnforcementConfig-err = proto.Unmarshal(entry.Value, &eConfig)-if err != nil {-return nil, err-}--return &eConfig, nil-}-var mfaHelp = map[string][2]string{"methods-list": {"Lists all the available MFA methods by their name.",
References
- ADVISORYhttps://github.com/advisories/GHSA-c5wc-v287-82pc
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2022-30689
- FIXhttps://github.com/hashicorp/vault/commit/15baea5fa3e71c837c33b8bcbd8f06e0fbbc110d
- WEBhttps://discuss.hashicorp.com
- WEBhttps://github.com/hashicorp/vault
- WEBhttps://security.gentoo.org/glsa/202207-01
- WEBhttps://security.netapp.com/advisory/ntap-20220629-0006