VYPR
Moderate severityNVD Advisory· Published Feb 4, 2022· Updated Apr 23, 2025

Limited ability to spoof SAML authentication with missing audience verification

CVE-2022-23600

Description

fleet is an open source device management, built on osquery. Versions prior to 4.9.1 expose a limited ability to spoof SAML authentication with missing audience verification. This impacts deployments using SAML SSO in two specific cases: 1. A malicious or compromised Service Provider (SP) could reuse the SAML response to log into Fleet as a user -- only if the user has an account with the same email in Fleet, _and_ the user signs into the malicious SP via SAML SSO from the same Identity Provider (IdP) configured with Fleet. 2. A user with an account in Fleet could reuse a SAML response intended for another SP to log into Fleet. This is only a concern if the user is blocked from Fleet in the IdP, but continues to have an account in Fleet. If the user is blocked from the IdP entirely, this cannot be exploited. Fleet 4.9.1 resolves this issue. Users unable to upgrade should: Reduce the length of sessions on your IdP to reduce the window for malicious re-use, Limit the amount of SAML Service Providers/Applications used by user accounts with access to Fleet, and When removing access to Fleet in the IdP, delete the Fleet user from Fleet as well.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/fleetdm/fleet/v4Go
< 4.9.14.9.1

Affected products

1

Patches

1
35d5a7b285f1

Merge pull request from GHSA-ch68-7cf4-35vr

https://github.com/fleetdm/fleetTomas ToucedaFeb 2, 2022via ghsa
5 files changed · +63 18
  • changes/sso-check-audience+1 0 added
    @@ -0,0 +1 @@
    +* Check for audience restrictions when validating a SAML request.
    
  • server/service/service_sessions.go+5 1 modified
    @@ -156,7 +156,11 @@ func (svc *Service) CallbackSSO(ctx context.Context, auth fleet.Auth) (*fleet.SS
     	}
     
     	// Validate response
    -	validator, err := sso.NewValidator(*metadata)
    +	validator, err := sso.NewValidator(*metadata, sso.WithExpectedAudience(
    +		appConfig.SSOSettings.EntityID,
    +		appConfig.ServerSettings.ServerURL,
    +		appConfig.ServerSettings.ServerURL+svc.config.Server.URLPrefix+"/api/v1/fleet/sso/callback", // ACS
    +	))
     	if err != nil {
     		return nil, ctxerr.Wrap(ctx, err, "create validator from metadata")
     	}
    
  • server/sso/types.go+8 3 modified
    @@ -205,9 +205,14 @@ type Assertion struct {
     }
     
     type Conditions struct {
    -	XMLName      xml.Name
    -	NotBefore    string `xml:",attr"`
    -	NotOnOrAfter string `xml:",attr"`
    +	XMLName             xml.Name
    +	NotBefore           string              `xml:",attr"`
    +	NotOnOrAfter        string              `xml:",attr"`
    +	AudienceRestriction AudienceRestriction `xml:"AudienceRestriction"`
    +}
    +
    +type AudienceRestriction struct {
    +	Audience string `xml:"Audience"`
     }
     
     type Subject struct {
    
  • server/sso/validate.go+22 3 modified
    @@ -24,9 +24,10 @@ type Validator interface {
     }
     
     type validator struct {
    -	context  *dsig.ValidationContext
    -	clock    *dsig.Clock
    -	metadata Metadata
    +	context           *dsig.ValidationContext
    +	clock             *dsig.Clock
    +	metadata          Metadata
    +	expectedAudiences []string
     }
     
     func Clock(clock *dsig.Clock) func(v *validator) {
    @@ -35,6 +36,12 @@ func Clock(clock *dsig.Clock) func(v *validator) {
     	}
     }
     
    +func WithExpectedAudience(audiences ...string) func(v *validator) {
    +	return func(v *validator) {
    +		v.expectedAudiences = audiences
    +	}
    +}
    +
     // NewValidator is used to validate the response to an auth request.
     // metadata is from the IDP.
     func NewValidator(metadata Metadata, opts ...func(v *validator)) (Validator, error) {
    @@ -86,6 +93,18 @@ func (v *validator) ValidateResponse(auth fleet.Auth) error {
     	if currentTime.Before(notBefore) {
     		return errors.New("response too early")
     	}
    +
    +	verifiesAudience := false
    +	for _, audience := range v.expectedAudiences {
    +		if info.response.Assertion.Conditions.AudienceRestriction.Audience == audience {
    +			verifiesAudience = true
    +			break
    +		}
    +	}
    +	if !verifiesAudience {
    +		return errors.New("wrong audience:" + info.response.Assertion.Conditions.AudienceRestriction.Audience)
    +	}
    +
     	if auth.UserID() == "" {
     		return errors.New("missing user id")
     	}
    
  • server/sso/validate_test.go+27 11 modified
    @@ -49,19 +49,35 @@ func TestValidate(t *testing.T) {
     	require.Nil(t, err)
     
     	clock := dsig.NewFakeClockAt(tm)
    -	validator, err := NewValidator(testMetadata(), Clock(clock))
    -	require.Nil(t, err)
    -	require.NotNil(t, validator)
     
    -	auth, err := DecodeAuthResponse(testResponse)
    -	require.Nil(t, err)
    +	testCases := []struct {
    +		audiences  []string
    +		shouldFail bool
    +	}{
    +		{audiences: []string{"kolide"}, shouldFail: false},
    +		{audiences: []string{"someotheraudience"}, shouldFail: true},
    +		{audiences: nil, shouldFail: true},
    +	}
     
    -	signed, err := validator.ValidateSignature(auth)
    -	require.Nil(t, err)
    -	require.NotNil(t, signed)
    +	for _, tt := range testCases {
    +		validator, err := NewValidator(testMetadata(), Clock(clock), WithExpectedAudience(tt.audiences...))
    +		require.Nil(t, err)
    +		require.NotNil(t, validator)
     
    -	err = validator.ValidateResponse(auth)
    -	assert.Nil(t, err)
    +		auth, err := DecodeAuthResponse(testResponse)
    +		require.Nil(t, err)
    +
    +		signed, err := validator.ValidateSignature(auth)
    +		require.Nil(t, err)
    +		require.NotNil(t, signed)
    +
    +		err = validator.ValidateResponse(auth)
    +		if tt.shouldFail {
    +			require.Error(t, err)
    +		} else {
    +			require.NoError(t, err)
    +		}
    +	}
     }
     
     func tamperedResponse(original string) (string, error) {
    @@ -169,7 +185,7 @@ func TestVerifyValidGoogleResponse(t *testing.T) {
     	tm, err := time.Parse(time.RFC3339, "2017-07-18T14:47:08.035Z")
     	require.Nil(t, err)
     	clock := dsig.NewFakeClockAt(tm)
    -	validator, err := NewValidator(testGoogleMetadata(), Clock(clock))
    +	validator, err := NewValidator(testGoogleMetadata(), Clock(clock), WithExpectedAudience("kolide.edilok.net"))
     	require.Nil(t, err)
     	require.NotNil(t, validator)
     	auth, err := DecodeAuthResponse(samlResponse)
    

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

4

News mentions

0

No linked articles in our index yet.