VYPR
Critical severityOSV Advisory· Published Jan 21, 2026· Updated Jan 22, 2026

Fleet has a JWT signature bypass vulnerability in Azure AD MDM enrollment

CVE-2026-23518

Description

Fleet is open source device management software. In versions prior to 4.78.3, 4.77.1, 4.76.2, 4.75.2, and 4.53.3, a vulnerability in Fleet's Windows MDM enrollment flow could allow an attacker to submit forged authentication tokens that are not properly validated. Because JWT signatures were not verified, Fleet could accept attacker-controlled identity claims, enabling enrollment of unauthorized devices under arbitrary Azure AD user identities. Versions 4.78.3, 4.77.1, 4.76.2, 4.75.2, and 4.53.3 fix the issue. If an immediate upgrade is not possible, affected Fleet users should temporarily disable Windows MDM.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/fleetdm/fleetGo
>= 4.78.0, < 4.78.34.78.3
github.com/fleetdm/fleetGo
>= 4.77.0, < 4.77.14.77.1
github.com/fleetdm/fleetGo
>= 4.76.0, < 4.76.24.76.2
github.com/fleetdm/fleetGo
>= 4.75.0, < 4.75.24.75.2
github.com/fleetdm/fleetGo
< 4.43.5-0.20260112202845-e225ef57912c4.43.5-0.20260112202845-e225ef57912c

Affected products

1
  • Range: fleet-v4.75.0, fleet-v4.75.1, fleet-v4.76.0, …

Patches

1
e225ef57912c

Improve Microsoft endpoint validation (#38180)

https://github.com/fleetdm/fleetJordan MontgomeryJan 12, 2026via ghsa
9 files changed · +254 38
  • changes/13698-windows-mdm+1 0 added
    @@ -0,0 +1 @@
    +* Improved SOAP message validation on Windows MDM endpoints
    
  • go.mod+1 0 modified
    @@ -10,6 +10,7 @@ require (
     	github.com/DATA-DOG/go-sqlmock v1.5.0
     	github.com/Masterminds/semver v1.5.0
     	github.com/Masterminds/semver/v3 v3.3.1
    +	github.com/MicahParks/jwkset v0.11.0
     	github.com/RobotsAndPencils/buford v0.14.0
     	github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f
     	github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea
    
  • go.sum+2 0 modified
    @@ -50,6 +50,8 @@ github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7r
     github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
     github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
     github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
    +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ=
    +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
     github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
     github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
     github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
    
  • pkg/mdm/mdmtest/windows.go+55 5 modified
    @@ -2,13 +2,16 @@ package mdmtest
     
     import (
     	"bytes"
    +	"crypto/rsa"
     	"crypto/tls"
     	"encoding/base64"
     	"encoding/xml"
    +	"errors"
     	"fmt"
     	"io"
     	"net/http"
     	"strconv"
    +	"strings"
     
     	"github.com/fleetdm/fleet/v4/pkg/fleethttp"
     	"github.com/fleetdm/fleet/v4/server/fleet"
    @@ -42,6 +45,10 @@ type TestWindowsMDMClient struct {
     	// queuedCommandResponses tracks the commands that will be sent next
     	// time the device responds to the server.
     	queuedCommandResponses map[string]fleet.SyncMLCmd
    +	// jwtSigningKey is the key used to sign JWTs
    +	jwtSigningKey *rsa.PrivateKey
    +	// jwtSigningKeyID is the ID to report in the header for the signing key
    +	jwtSigningKeyID string
     }
     
     // This is a test-only enrollment type to force erroneous behavior.
    @@ -65,6 +72,13 @@ func TestWindowsMDMClientNotInOOBE() TestWindowsMDMClientOption {
     	}
     }
     
    +func TestWindowsMDMClientWithSigningKey(signingKey *rsa.PrivateKey, signingKeyID string) TestWindowsMDMClientOption {
    +	return func(c *TestWindowsMDMClient) {
    +		c.jwtSigningKey = signingKey
    +		c.jwtSigningKeyID = signingKeyID
    +	}
    +}
    +
     func NewTestMDMClientWindowsProgramatic(serverURL string, orbitNodeKey string, opts ...TestWindowsMDMClientOption) *TestWindowsMDMClient {
     	return newTestMDMClient(serverURL, fleet.WindowsMDMProgrammaticEnrollmentType, orbitNodeKey, opts...)
     }
    @@ -374,10 +388,21 @@ YioVozr1IWYySwWVzMf/SUwKZkKJCAJmSVcixE+4kxPkyPGyauIrN3wWC0zb+mjF
     </s:Envelope>
     `)
     
    -	if _, err := c.request(microsoft_mdm.MDE2EnrollPath, enrollReq); err != nil {
    +	resp, err := c.request(microsoft_mdm.MDE2EnrollPath, enrollReq)
    +	if err != nil {
     		return err
     	}
     
    +	// Check for SOAP fault
    +	defer resp.Body.Close()
    +	body, err := io.ReadAll(resp.Body)
    +	if err != nil {
    +		return err
    +	}
    +	if strings.Contains(string(body), "s:fault") {
    +		return fmt.Errorf("enroll request returned SOAP fault: %s", string(body))
    +	}
    +
     	return nil
     }
     
    @@ -416,10 +441,21 @@ func (c *TestWindowsMDMClient) Discovery() error {
     
     	// TODO: parse the response and store the policy and enroll endpoints instead
     	// of hardcoding them to truly test that the server is behaving as expected.
    -	if _, err := c.request(microsoft_mdm.MDE2DiscoveryPath, discoveryReq); err != nil {
    +	resp, err := c.request(microsoft_mdm.MDE2DiscoveryPath, discoveryReq)
    +	if err != nil {
     		return err
     	}
     
    +	// Check for SOAP fault
    +	defer resp.Body.Close()
    +	body, err := io.ReadAll(resp.Body)
    +	if err != nil {
    +		return err
    +	}
    +	if strings.Contains(string(body), "s:fault") {
    +		return fmt.Errorf("discovery request returned SOAP fault: %s", string(body))
    +	}
    +
     	return nil
     }
     
    @@ -467,9 +503,19 @@ func (c *TestWindowsMDMClient) Policy() error {
     
     	// TODO: store the policy requirements to generate a certificate and generate
     	// one on the fly using them instead of using hardcoded values.
    -	if _, err := c.request(microsoft_mdm.MDE2PolicyPath, policyReq); err != nil {
    +	resp, err := c.request(microsoft_mdm.MDE2PolicyPath, policyReq)
    +	if err != nil {
     		return err
     	}
    +	// Check for SOAP fault
    +	defer resp.Body.Close()
    +	body, err := io.ReadAll(resp.Body)
    +	if err != nil {
    +		return err
    +	}
    +	if strings.Contains(string(body), "s:fault") {
    +		return fmt.Errorf("policy request returned SOAP fault: %s", string(body))
    +	}
     
     	return nil
     }
    @@ -503,9 +549,13 @@ func (c *TestWindowsMDMClient) getToken() (binarySecToken string, tokenValueType
     			"unique_name": "foo_bar",
     			"scp":         "mdm_delegation",
     		}
    +		if c.jwtSigningKey == nil || c.jwtSigningKeyID == "" {
    +			return "", "", errors.New("jwt signing key is not set")
    +		}
     
    -		token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    -		tokenString, err := token.SignedString([]byte("foo"))
    +		token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
    +		token.Header["kid"] = c.jwtSigningKeyID
    +		tokenString, err := token.SignedString(c.jwtSigningKey)
     		if err != nil {
     			return "", "", err
     		}
    
  • server/mdm/microsoft/wstep.go+58 11 modified
    @@ -13,11 +13,14 @@ import (
     	"errors"
     	"fmt"
     	"math/big"
    +	"os"
     	"strconv"
     	"strings"
     	"time"
     
    +	"github.com/MicahParks/jwkset"
     	"github.com/fleetdm/fleet/v4/server"
    +	"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
     	"github.com/fleetdm/fleet/v4/server/mdm/microsoft/syncml"
     	"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/cryptoutil"
     	"github.com/golang-jwt/jwt/v4"
    @@ -215,7 +218,7 @@ func (m *manager) GetSTSAuthTokenUPNClaim(tokenStr string) (string, error) {
     	}
     
     	// Since we used the private key to sign the tokens, we use the public counterpart to verify the signature
    -	token, err := jwt.ParseWithClaims(tokenStr, &STSClaims{}, func(token *jwt.Token) (interface{}, error) {
    +	token, err := jwt.ParseWithClaims(tokenStr, &STSClaims{}, func(token *jwt.Token) (any, error) {
     		return m.identityCert.PublicKey, nil
     	})
     	if err != nil {
    @@ -235,27 +238,71 @@ func (m *manager) GetSTSAuthTokenUPNClaim(tokenStr string) (string, error) {
     
     // GetAzureAuthTokenClaims validates the given Azure AD token and returns
     // UPN, TenantID, UniqueName, DeviceID
    -func GetAzureAuthTokenClaims(tokenStr string) (AzureData, error) {
    +func GetAzureAuthTokenClaims(ctx context.Context, tokenStr string) (AzureData, error) {
     	if len(tokenStr) == 0 {
    -		return AzureData{}, errors.New("invalid STS token")
    +		return AzureData{}, ctxerr.New(ctx, "invalid STS token")
     	}
     
     	// Decode base64 token
     	tokenBytes, err := base64.StdEncoding.DecodeString(tokenStr)
     	if err != nil {
    -		return AzureData{}, errors.New("invalid Azure JWT token")
    +		return AzureData{}, ctxerr.Wrap(ctx, err, "invalid Azure JWT token")
     	}
     
     	// Validate token format (header.payload.signature)
     	parts := bytes.Split(tokenBytes, []byte("."))
     	if len(parts) != 3 {
    -		return AzureData{}, errors.New("invalid Azure JWT format")
    +		return AzureData{}, ctxerr.New(ctx, "invalid Azure JWT format")
     	}
     
     	// Parse JWT token
    -	token, _, err := new(jwt.Parser).ParseUnverified(string(tokenBytes), jwt.MapClaims{})
    +	jwksURI := "https://login.microsoftonline.com/common/discovery/v2.0/keys"
    +	var token *jwt.Token
    +	FLEET_DEV_AZURE_JWT_JWKS_URI := os.Getenv("FLEET_DEV_AZURE_JWT_JWKS_URI")
    +	if FLEET_DEV_AZURE_JWT_JWKS_URI != "" {
    +		jwksURI = FLEET_DEV_AZURE_JWT_JWKS_URI
    +	}
    +
    +	keys, err := jwkset.NewDefaultHTTPClient([]string{jwksURI})
    +	if err != nil {
    +		return AzureData{}, ctxerr.Wrap(ctx, err, "failed to retrieve Azure JWT signing keys")
    +	}
    +	token, err = jwt.Parse(string(tokenBytes), func(token *jwt.Token) (any, error) {
    +		tokenAlg, ok := token.Header["alg"]
    +		if !ok {
    +			return nil, errors.New("Azure JWT missing alg header")
    +		}
    +		tokenAlgStr, ok := tokenAlg.(string)
    +		if !ok {
    +			return nil, errors.New("invalid alg header in Azure JWT")
    +		}
    +
    +		kid, ok := token.Header["kid"]
    +		if !ok {
    +			return nil, errors.New("Azure JWT missing kid header")
    +		}
    +		kidStr, ok := kid.(string)
    +		if !ok {
    +			return nil, errors.New("invalid kid header in Azure JWT")
    +		}
    +
    +		key, err := keys.KeyRead(ctx, kidStr)
    +		if err != nil {
    +			if errors.Is(err, jwkset.ErrKeyNotFound) {
    +				return nil, fmt.Errorf("Azure JWT signed by unknown key: %w", err)
    +			}
    +			return nil, fmt.Errorf("failed to retrieve Azure JWT signing key: %w", err)
    +		}
    +
    +		// Alg is optional in the JWK but if present must match the token
    +		keyAlg := key.Marshal().ALG.String()
    +		if keyAlg != "" && keyAlg != tokenAlgStr {
    +			return nil, fmt.Errorf("Azure JWT signing key algorithm mismatch: expected %s from key, got %s", keyAlg, tokenAlgStr)
    +		}
    +		return key.Key(), nil
    +	})
     	if err != nil {
    -		return AzureData{}, errors.New("parse error Azure JWT content")
    +		return AzureData{}, ctxerr.Wrap(ctx, err, "parse error Azure JWT content")
     	}
     
     	// Parse JWT token
    @@ -264,25 +311,25 @@ func GetAzureAuthTokenClaims(tokenStr string) (AzureData, error) {
     	// Get UPN claim
     	upnClaim, ok := claims["upn"].(string)
     	if !ok || len(upnClaim) == 0 {
    -		return AzureData{}, errors.New("invalid UPN claim")
    +		return AzureData{}, ctxerr.New(ctx, "invalid UPN claim")
     	}
     
     	// Get TenantID claim
     	tenantIDClaim, ok := claims["tid"].(string)
     	if !ok || len(tenantIDClaim) == 0 {
    -		return AzureData{}, errors.New("invalid TenantID claim")
    +		return AzureData{}, ctxerr.New(ctx, "invalid TenantID claim")
     	}
     
     	// Get UniqueName claim
     	uniqueNameClaim, ok := claims["unique_name"].(string)
     	if !ok {
    -		return AzureData{}, errors.New("invalid UniqueName claim")
    +		return AzureData{}, ctxerr.New(ctx, "invalid UniqueName claim")
     	}
     
     	// Get SCP claim
     	azureSCPClaim, ok := claims["scp"].(string)
     	if !ok || azureSCPClaim != "mdm_delegation" {
    -		return AzureData{}, errors.New("invalid SCP claim")
    +		return AzureData{}, ctxerr.New(ctx, "invalid SCP claim")
     	}
     
     	return AzureData{
    
  • server/service/integration_logger_test.go+4 5 modified
    @@ -371,7 +371,7 @@ func (s *integrationLoggerTestSuite) TestWindowsMDMEnrollEmptyBinarySecurityToke
     	host := createOrbitEnrolledHost(t, "windows", "", s.ds)
     	mdmDevice := mdmtest.NewTestMDMClientWindowsEmptyBinarySecurityToken(s.server.URL, *host.OrbitNodeKey)
     	err = mdmDevice.Enroll()
    -	require.NoError(t, err)
    +	require.Error(t, err)
     
     	t.Log(s.buf.String())
     
    @@ -394,12 +394,11 @@ func (s *integrationLoggerTestSuite) TestWindowsMDMEnrollEmptyBinarySecurityToke
     			require.Equal(t, "info", m["level"])
     			require.Equal(t, "binarySecurityToken is empty", m["soap_fault"])
     		case microsoft_mdm.MDE2EnrollPath:
    -			require.Equal(t, "info", m["level"])
    -			require.Equal(t, "binarySecurityToken is empty", m["soap_fault"])
    -			foundEnroll = true
    +			foundEnroll = false
     		}
     	}
     	require.True(t, foundDiscovery)
     	require.True(t, foundPolicy)
    -	require.True(t, foundEnroll)
    +	// Will not enroll due to soap fault on prior request
    +	require.False(t, foundEnroll)
     }
    
  • server/service/integration_mdm_lifecycle_test.go+27 2 modified
    @@ -327,6 +327,16 @@ func (s *integrationMDMTestSuite) TestTurnOnLifecycleEventsWindows() {
     				// the ack of the message should be the only returned command
     				require.Len(t, cmds, 1)
     
    +				// Simulate the host having fleetd installed and reporting back in as un-enrolled
    +				mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
    +					_, err := q.ExecContext(context.Background(), `
    +	              UPDATE host_mdm
    +	              SET enrolled = 0, server_url = ''
    +	              WHERE host_id = ?
    +		`, host.ID)
    +					return err
    +				})
    +
     				// re-enroll
     				require.NoError(t, device.Enroll())
     			},
    @@ -359,13 +369,28 @@ func (s *integrationMDMTestSuite) TestTurnOnLifecycleEventsWindows() {
     					&orbitScriptResp,
     				)
     
    +				// Simulate the host having fleetd installed after being wiped and reporting back in as un-enrolled
    +				mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
    +					_, err := q.ExecContext(context.Background(), `
    +	              UPDATE host_mdm
    +	              SET enrolled = 0, server_url = ''
    +	              WHERE host_id = ?
    +		`, host.ID)
    +					return err
    +				})
    +
     				require.NoError(t, device.Enroll())
     			},
     		},
     		{
     			"host turns on MDM features out of the blue",
     			func(t *testing.T, host *fleet.Host, device *mdmtest.TestWindowsMDMClient) {
    -				require.NoError(t, device.Enroll())
    +				if strings.Contains(t.Name(), "automatic") {
    +					require.NoError(t, device.Enroll())
    +				} else {
    +					// A programatically-enrolled host that randomly turns on MDM after already enabled will get a SOAP fault
    +					require.Error(t, device.Enroll())
    +				}
     			},
     		},
     		{
    @@ -438,7 +463,7 @@ func (s *integrationMDMTestSuite) TestTurnOnLifecycleEventsWindows() {
     				host := createOrbitEnrolledHost(t, "windows", "windows_automatic", s.ds)
     
     				azureMail := "foo.bar.baz@example.com"
    -				device := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, azureMail)
    +				device := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, azureMail, mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, defaultFakeJWTKeyID))
     				device.HardwareID = host.UUID
     				device.DeviceID = host.UUID
     				require.NoError(t, device.Enroll())
    
  • server/service/integration_mdm_test.go+105 14 modified
    @@ -35,12 +35,14 @@ import (
     	"testing"
     	"time"
     
    +	"github.com/MicahParks/jwkset"
     	"github.com/davecgh/go-spew/spew"
     	"github.com/fleetdm/fleet/v4/server/mdm/android"
     	android_mock "github.com/fleetdm/fleet/v4/server/mdm/android/mock"
     	android_service "github.com/fleetdm/fleet/v4/server/mdm/android/service"
     	"github.com/fleetdm/fleet/v4/server/mdm/android/service/androidmgmt"
     	"github.com/fleetdm/fleet/v4/server/mdm/android/tests"
    +	"github.com/golang-jwt/jwt/v4"
     	"google.golang.org/api/androidmanagement/v1"
     
     	eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
    @@ -131,6 +133,7 @@ type integrationMDMTestSuite struct {
     	androidAPIClient          *android_mock.Client
     	androidSvc                android.Service
     	proxyCallbackURL          string
    +	jwtSigningKey             *rsa.PrivateKey
     }
     
     // appleVPPConfigSrvConf is used to configure the mock server that mocks Apple's VPP endpoints.
    @@ -158,6 +161,8 @@ var defaultVPPAssetList = []vpp.Asset{
     	},
     }
     
    +const defaultFakeJWTKeyID = "fleetdefaultkeyid"
    +
     func (s *integrationMDMTestSuite) SetupSuite() {
     	s.withDS.SetupSuite("integrationMDMTestSuite")
     
    @@ -734,6 +739,37 @@ func (s *integrationMDMTestSuite) SetupSuite() {
     	s.T().Cleanup(s.appleVPPConfigSrv.Close)
     	s.T().Cleanup(s.appleVPPProxySrv.Close)
     	s.T().Cleanup(s.appleGDMFSrv.Close)
    +
    +	// Setup fake JWKs server for Azure JWT verification
    +	metadata := jwkset.JWKMetadataOptions{
    +		KID: defaultFakeJWTKeyID,
    +	}
    +	options := jwkset.JWKOptions{
    +		Metadata: metadata,
    +	}
    +
    +	// Create the JWK from the key and options.
    +	jwk, err := jwkset.NewJWKFromKey(testKey, options)
    +	require.NoError(s.T(), err)
    +
    +	jwkSet := jwkset.NewMemoryStorage()
    +
    +	err = jwkSet.KeyWrite(ctx, jwk)
    +	require.NoError(s.T(), err)
    +
    +	jwksServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    +		if r.URL.Path == "/jwks.json" {
    +			response, err := jwkSet.JSONPublic(r.Context())
    +			require.NoError(s.T(), err)
    +
    +			w.Header().Set("Content-Type", "application/json")
    +			_, err = w.Write(response)
    +			require.NoError(s.T(), err)
    +		}
    +	}))
    +	s.T().Cleanup(jwksServer.Close)
    +	s.jwtSigningKey = testKey
    +	s.T().Setenv("FLEET_DEV_AZURE_JWT_JWKS_URI", jwksServer.URL+"/jwks.json")
     }
     
     func (s *integrationMDMTestSuite) TearDownSuite() {
    @@ -1491,25 +1527,25 @@ func enrollWindowsHostInMDMViaOrbit(t *testing.T, host *fleet.Host, ds fleet.Dat
     }
     
     // Simulates a host being orbit enrolled first then an MDM enrollment coming via the settings app
    -func createWindowsHostThenEnrollMDMViaSettingsApp(ds fleet.Datastore, fleetServerURL, email string, t *testing.T) (*fleet.Host, *mdmtest.TestWindowsMDMClient) {
    -	host := createOrbitEnrolledHost(t, "windows", uuid.NewString(), ds)
    -	mdmDevice := enrollWindowsMDMViaSettingsApp(t, ds, fleetServerURL, email)
    +func (s *integrationMDMTestSuite) createWindowsHostThenEnrollMDMViaSettingsApp(fleetServerURL, email string) (*fleet.Host, *mdmtest.TestWindowsMDMClient) {
    +	host := createOrbitEnrolledHost(s.T(), "windows", uuid.NewString(), s.ds)
    +	mdmDevice := s.enrollWindowsMDMViaSettingsApp(fleetServerURL, email)
     	return host, mdmDevice
     }
     
     // Note that this method only creates the MDM Enrollment but it will still need to be linked to the host record either
     // via DS methods or by simualting a refetch.
    -func enrollWindowsMDMViaSettingsApp(t *testing.T, ds fleet.Datastore, fleetServerURL, email string) *mdmtest.TestWindowsMDMClient {
    -	mdmDevice := mdmtest.NewTestMDMClientWindowsAutomatic(fleetServerURL, email, mdmtest.TestWindowsMDMClientNotInOOBE())
    +func (s *integrationMDMTestSuite) enrollWindowsMDMViaSettingsApp(fleetServerURL, email string) *mdmtest.TestWindowsMDMClient {
    +	mdmDevice := mdmtest.NewTestMDMClientWindowsAutomatic(fleetServerURL, email, mdmtest.TestWindowsMDMClientNotInOOBE(), mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, defaultFakeJWTKeyID))
     	err := mdmDevice.Enroll()
    -	require.NoError(t, err)
    +	require.NoError(s.T(), err)
     	return mdmDevice
     }
     
    -func enrollWindowsHostInMDMViaAutopilot(t *testing.T, ds fleet.Datastore, fleetServerURL, email string) *mdmtest.TestWindowsMDMClient {
    -	mdmDevice := mdmtest.NewTestMDMClientWindowsAutomatic(fleetServerURL, email)
    +func (s *integrationMDMTestSuite) enrollWindowsHostInMDMViaAutopilot(fleetServerURL, email string) *mdmtest.TestWindowsMDMClient {
    +	mdmDevice := mdmtest.NewTestMDMClientWindowsAutomatic(fleetServerURL, email, mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, defaultFakeJWTKeyID))
     	err := mdmDevice.Enroll()
    -	require.NoError(t, err)
    +	require.NoError(s.T(), err)
     	return mdmDevice
     }
     
    @@ -7663,7 +7699,20 @@ func (s *integrationMDMTestSuite) TestValidGetPoliciesRequestWithAzureToken() {
     	t := s.T()
     
     	// Preparing the GetPolicies Request message with Azure JWT token
    -	azureADTok := "ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUlzSW5nMWRDSTZJaTFMU1ROUk9XNU9VamRpVW05bWVHMWxXbTlZY1dKSVdrZGxkeUlzSW10cFpDSTZJaTFMU1ROUk9XNU9VamRpVW05bWVHMWxXbTlZY1dKSVdrZGxkeUo5LmV5SmhkV1FpT2lKb2RIUndjem92TDIxaGNtTnZjMnhoWW5NdWIzSm5MeUlzSW1semN5STZJbWgwZEhCek9pOHZjM1J6TG5kcGJtUnZkM011Ym1WMEwyWmhaVFZqTkdZekxXWXpNVGd0TkRRNE15MWlZelptTFRjMU9UVTFaalJoTUdFM01pOGlMQ0pwWVhRaU9qRTJPRGt4TnpBNE5UZ3NJbTVpWmlJNk1UWTRPVEUzTURnMU9Dd2laWGh3SWpveE5qZzVNVGMxTmpZeExDSmhZM0lpT2lJeElpd2lZV2x2SWpvaVFWUlJRWGt2T0ZSQlFVRkJOV2gwUTNFMGRERjNjbHBwUTIxQmVEQlpWaTloZGpGTVMwRkRPRXM1Vm10SGVtNUdXVGxzTUZoYWVrZHVha2N6VVRaMWVIUldNR3QxT1hCeFJXdFRZeUlzSW1GdGNpSTZXeUp3ZDJRaUxDSnljMkVpWFN3aVlYQndhV1FpT2lJeU9XUTVaV1E1T0MxaE5EWTVMVFExTXpZdFlXUmxNaTFtT1RneFltTXhaRFl3TldVaUxDSmhjSEJwWkdGamNpSTZJakFpTENKa1pYWnBZMlZwWkNJNkltRXhNMlkzWVdVd0xURXpPR0V0TkdKaU1pMDVNalF5TFRka09USXlaVGRqTkdGak15SXNJbWx3WVdSa2NpSTZJakU0Tmk0eE1pNHhPRGN1TWpZaUxDSnVZVzFsSWpvaVZHVnpkRTFoY21OdmMweGhZbk1pTENKdmFXUWlPaUpsTTJNMU5XVmtZeTFqTXpRNExUUTBNVFl0T0dZd05TMHlOVFJtWmpNd05qVmpOV1VpTENKd2QyUmZkWEpzSWpvaWFIUjBjSE02THk5d2IzSjBZV3d1YldsamNtOXpiMlowYjI1c2FXNWxMbU52YlM5RGFHRnVaMlZRWVhOemQyOXlaQzVoYzNCNElpd2ljbWdpT2lJd0xrRldTVUU0T0ZSc0xXaHFlbWN3VXpoaU0xZFdXREJ2UzJOdFZGRXpTbHB1ZUUxa1QzQTNUbVZVVm5OV2FYVkhOa0ZRYnk0aUxDSnpZM0FpT2lKdFpHMWZaR1ZzWldkaGRHbHZiaUlzSW5OMVlpSTZJa1pTUTJ4RldURk9ObXR2ZEdWblMzcFplV0pFTjJkdFdGbGxhVTVIUkZrd05FSjJOV3R6ZDJGeGJVRWlMQ0owYVdRaU9pSm1ZV1UxWXpSbU15MW1NekU0TFRRME9ETXRZbU0yWmkwM05UazFOV1kwWVRCaE56SWlMQ0oxYm1seGRXVmZibUZ0WlNJNkluUmxjM1JBYldGeVkyOXpiR0ZpY3k1dmNtY2lMQ0oxY0c0aU9pSjBaWE4wUUcxaGNtTnZjMnhoWW5NdWIzSm5JaXdpZFhScElqb2lNVGg2WkVWSU5UZFRSWFZyYWpseGJqRm9aMlJCUVNJc0luWmxjaUk2SWpFdU1DSjkuVG1FUlRsZktBdWo5bTVvQUc2UTBRblV4VEFEaTNFamtlNHZ3VXo3UTdqUUFVZVZGZzl1U0pzUXNjU2hFTXVxUmQzN1R2VlpQanljdEVoRFgwLVpQcEVVYUlSempuRVEyTWxvc21SZURYZzhrYkhNZVliWi1jb0ZucDEyQkVpQnpJWFBGZnBpaU1GRnNZZ0hSSF9tSWxwYlBlRzJuQ2p0LTZSOHgzYVA5QS1tM0J3eV91dnV0WDFNVEVZRmFsekhGa04wNWkzbjZRcjhURnlJQ1ZUYW5OanlkMjBBZFRMbHJpTVk0RVBmZzRaLThVVTctZkcteElycWVPUmVWTnYwOUFHV192MDd6UkVaNmgxVk9tNl9nelRGcElVVURuZFdabnFLTHlySDlkdkF3WnFFSG1HUmlTNElNWnRFdDJNTkVZSnhDWHhlSi1VbWZJdV9tUVhKMW9R"
    +	// Preparing the SecurityToken Request message with Azure JWT token
    +	claims := &jwt.MapClaims{
    +		"upn":         "fleetie@example.com",
    +		"tid":         "tenant_id",
    +		"unique_name": "foo_bar",
    +		"scp":         "mdm_delegation",
    +	}
    +
    +	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
    +	token.Header["kid"] = defaultFakeJWTKeyID
    +	tokenString, err := token.SignedString(s.jwtSigningKey)
    +	require.NoError(t, err)
    +
    +	azureADTok := base64.URLEncoding.EncodeToString([]byte(tokenString))
     	requestBytes, err := s.newGetPoliciesMsg(false, azureADTok)
     	require.NoError(t, err)
     
    @@ -7826,7 +7875,19 @@ func (s *integrationMDMTestSuite) TestValidRequestSecurityTokenRequestWithAzureT
     	t := s.T()
     
     	// Preparing the SecurityToken Request message with Azure JWT token
    -	azureADTok := "ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKU1V6STFOaUlzSW5nMWRDSTZJaTFMU1ROUk9XNU9VamRpVW05bWVHMWxXbTlZY1dKSVdrZGxkeUlzSW10cFpDSTZJaTFMU1ROUk9XNU9VamRpVW05bWVHMWxXbTlZY1dKSVdrZGxkeUo5LmV5SmhkV1FpT2lKb2RIUndjem92TDIxaGNtTnZjMnhoWW5NdWIzSm5MeUlzSW1semN5STZJbWgwZEhCek9pOHZjM1J6TG5kcGJtUnZkM011Ym1WMEwyWmhaVFZqTkdZekxXWXpNVGd0TkRRNE15MWlZelptTFRjMU9UVTFaalJoTUdFM01pOGlMQ0pwWVhRaU9qRTJPRGt4TnpBNE5UZ3NJbTVpWmlJNk1UWTRPVEUzTURnMU9Dd2laWGh3SWpveE5qZzVNVGMxTmpZeExDSmhZM0lpT2lJeElpd2lZV2x2SWpvaVFWUlJRWGt2T0ZSQlFVRkJOV2gwUTNFMGRERjNjbHBwUTIxQmVEQlpWaTloZGpGTVMwRkRPRXM1Vm10SGVtNUdXVGxzTUZoYWVrZHVha2N6VVRaMWVIUldNR3QxT1hCeFJXdFRZeUlzSW1GdGNpSTZXeUp3ZDJRaUxDSnljMkVpWFN3aVlYQndhV1FpT2lJeU9XUTVaV1E1T0MxaE5EWTVMVFExTXpZdFlXUmxNaTFtT1RneFltTXhaRFl3TldVaUxDSmhjSEJwWkdGamNpSTZJakFpTENKa1pYWnBZMlZwWkNJNkltRXhNMlkzWVdVd0xURXpPR0V0TkdKaU1pMDVNalF5TFRka09USXlaVGRqTkdGak15SXNJbWx3WVdSa2NpSTZJakU0Tmk0eE1pNHhPRGN1TWpZaUxDSnVZVzFsSWpvaVZHVnpkRTFoY21OdmMweGhZbk1pTENKdmFXUWlPaUpsTTJNMU5XVmtZeTFqTXpRNExUUTBNVFl0T0dZd05TMHlOVFJtWmpNd05qVmpOV1VpTENKd2QyUmZkWEpzSWpvaWFIUjBjSE02THk5d2IzSjBZV3d1YldsamNtOXpiMlowYjI1c2FXNWxMbU52YlM5RGFHRnVaMlZRWVhOemQyOXlaQzVoYzNCNElpd2ljbWdpT2lJd0xrRldTVUU0T0ZSc0xXaHFlbWN3VXpoaU0xZFdXREJ2UzJOdFZGRXpTbHB1ZUUxa1QzQTNUbVZVVm5OV2FYVkhOa0ZRYnk0aUxDSnpZM0FpT2lKdFpHMWZaR1ZzWldkaGRHbHZiaUlzSW5OMVlpSTZJa1pTUTJ4RldURk9ObXR2ZEdWblMzcFplV0pFTjJkdFdGbGxhVTVIUkZrd05FSjJOV3R6ZDJGeGJVRWlMQ0owYVdRaU9pSm1ZV1UxWXpSbU15MW1NekU0TFRRME9ETXRZbU0yWmkwM05UazFOV1kwWVRCaE56SWlMQ0oxYm1seGRXVmZibUZ0WlNJNkluUmxjM1JBYldGeVkyOXpiR0ZpY3k1dmNtY2lMQ0oxY0c0aU9pSjBaWE4wUUcxaGNtTnZjMnhoWW5NdWIzSm5JaXdpZFhScElqb2lNVGg2WkVWSU5UZFRSWFZyYWpseGJqRm9aMlJCUVNJc0luWmxjaUk2SWpFdU1DSjkuVG1FUlRsZktBdWo5bTVvQUc2UTBRblV4VEFEaTNFamtlNHZ3VXo3UTdqUUFVZVZGZzl1U0pzUXNjU2hFTXVxUmQzN1R2VlpQanljdEVoRFgwLVpQcEVVYUlSempuRVEyTWxvc21SZURYZzhrYkhNZVliWi1jb0ZucDEyQkVpQnpJWFBGZnBpaU1GRnNZZ0hSSF9tSWxwYlBlRzJuQ2p0LTZSOHgzYVA5QS1tM0J3eV91dnV0WDFNVEVZRmFsekhGa04wNWkzbjZRcjhURnlJQ1ZUYW5OanlkMjBBZFRMbHJpTVk0RVBmZzRaLThVVTctZkcteElycWVPUmVWTnYwOUFHV192MDd6UkVaNmgxVk9tNl9nelRGcElVVURuZFdabnFLTHlySDlkdkF3WnFFSG1HUmlTNElNWnRFdDJNTkVZSnhDWHhlSi1VbWZJdV9tUVhKMW9R"
    +	claims := &jwt.MapClaims{
    +		"upn":         "fleetie@example.com",
    +		"tid":         "tenant_id",
    +		"unique_name": "foo_bar",
    +		"scp":         "mdm_delegation",
    +	}
    +
    +	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
    +	token.Header["kid"] = defaultFakeJWTKeyID
    +	tokenString, err := token.SignedString(s.jwtSigningKey)
    +	require.NoError(t, err)
    +
    +	azureADTok := base64.URLEncoding.EncodeToString([]byte(tokenString))
     	requestBytes, err := s.newSecurityTokenMsg(azureADTok, false, false)
     	require.NoError(t, err)
     
    @@ -8396,7 +8457,7 @@ func (s *integrationMDMTestSuite) TestWindowsAutomaticEnrollmentCommands() {
     	require.NoError(t, err)
     
     	azureMail := "foo.bar.baz@example.com"
    -	d := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, azureMail)
    +	d := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, azureMail, mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, defaultFakeJWTKeyID))
     	require.NoError(t, d.Enroll())
     
     	checkinAndAck := func(expectFleetdCmds bool) {
    @@ -8487,6 +8548,36 @@ func (s *integrationMDMTestSuite) TestWindowsAutomaticEnrollmentCommands() {
     	checkinAndAck(false)
     }
     
    +func (s *integrationMDMTestSuite) TestWindowsAzureInitiatedBadKeys() {
    +	t := s.T()
    +	ctx := context.Background()
    +
    +	// define a global enroll secret
    +	err := s.ds.ApplyEnrollSecrets(ctx, nil, []*fleet.EnrollSecret{{Secret: t.Name()}})
    +	require.NoError(t, err)
    +
    +	// We could generate the key differently, this just ensures it's generated the same as the "happy path" tests
    +	_, badKey, err := apple_mdm.NewSCEPCACertKey()
    +	require.NoError(t, err)
    +
    +	autopilotUserMail := "swan@example.com"
    +
    +	// Bad key
    +	mdmDevice := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, autopilotUserMail, mdmtest.TestWindowsMDMClientWithSigningKey(badKey, defaultFakeJWTKeyID))
    +	err = mdmDevice.Enroll()
    +	require.Error(s.T(), err)
    +
    +	// Good key but wrong ID
    +	mdmDevice = mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, autopilotUserMail, mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, "bad-key-id"))
    +	err = mdmDevice.Enroll()
    +	require.Error(s.T(), err)
    +
    +	// Happy path to ensure the setup is correct
    +	mdmDevice = mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, autopilotUserMail, mdmtest.TestWindowsMDMClientWithSigningKey(s.jwtSigningKey, defaultFakeJWTKeyID))
    +	err = mdmDevice.Enroll()
    +	require.NoError(s.T(), err)
    +}
    +
     func (s *integrationMDMTestSuite) TestWindowsAzureInitiatedEnrollmentAndMapping() {
     	t := s.T()
     	ctx := context.Background()
    @@ -8503,11 +8594,11 @@ func (s *integrationMDMTestSuite) TestWindowsAzureInitiatedEnrollmentAndMapping(
     
     	// Enroll another host to ensure the wires don't get crossed somehow
     	autopilotUserMail := "swan@example.com"
    -	autopilotDevice := enrollWindowsHostInMDMViaAutopilot(t, s.ds, s.server.URL, autopilotUserMail)
    +	autopilotDevice := s.enrollWindowsHostInMDMViaAutopilot(s.server.URL, autopilotUserMail)
     	require.NoError(t, autopilotDevice.Enroll())
     
     	settingsAppUserMail := "fleetie@example.com"
    -	settingsAppHost, settingsAppDevice := createWindowsHostThenEnrollMDMViaSettingsApp(s.ds, s.server.URL, settingsAppUserMail, t)
    +	settingsAppHost, settingsAppDevice := s.createWindowsHostThenEnrollMDMViaSettingsApp(s.server.URL, settingsAppUserMail)
     	require.NoError(t, settingsAppDevice.Enroll())
     
     	// Transfer the host to the team. Ensure it doesn't wind up in "No team" at the end
    
  • server/service/microsoft_mdm.go+1 1 modified
    @@ -1021,7 +1021,7 @@ func (svc *Service) authBinarySecurityToken(ctx context.Context, authToken *flee
     	if authToken.IsAzureJWTToken() {
     
     		// Validate the JWT Auth token by retreving its claims
    -		tokenData, err := microsoft_mdm.GetAzureAuthTokenClaims(authToken.Content)
    +		tokenData, err := microsoft_mdm.GetAzureAuthTokenClaims(ctx, authToken.Content)
     		if err != nil {
     			return "", "", fmt.Errorf("binary security token claim failed: %v", err)
     		}
    

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

5

News mentions

0

No linked articles in our index yet.