Fleet Windows MDM endpoint has a Cross-site Scripting vulnerability
Description
fleetdm/fleet is open source device management software. Prior to versions 4.78.2, 4.77.1, 4.76.2, 4.75.2, and 4.53.3, if Windows MDM is enabled, an unauthenticated attacker can exploit this XSS vulnerability to steal a Fleet administrator's authentication token (FLEET::auth_token) from localStorage. This could allow unauthorized access to Fleet, including administrative access, visibility into device data, and modification of configuration. Versions 4.78.2, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/fleetdm/fleetGo | >= 4.78.0, < 4.78.2 | 4.78.2 |
github.com/fleetdm/fleetGo | >= 4.77.0, < 4.77.1 | 4.77.1 |
github.com/fleetdm/fleetGo | >= 4.76.0, < 4.76.2 | 4.76.2 |
github.com/fleetdm/fleetGo | >= 4.75.0, < 4.75.2 | 4.75.2 |
github.com/fleetdm/fleet/v4Go | < 4.43.5-0.20260111020427-0e6c790803d1 | 4.43.5-0.20260111020427-0e6c790803d1 |
Affected products
1Patches
10e6c790803d1Add additional validation to mdmMicrosoftAuthEndpoint (#38147)
4 files changed · +169 −3
changes/fix-redirect-url-handling+1 −0 added@@ -0,0 +1 @@ +* Added additional validation to URL parameter for MS MDM auth endpoint
server/service/integration_mdm_test.go+75 −1 modified@@ -7917,7 +7917,8 @@ func (s *integrationMDMTestSuite) TestValidGetAuthRequest() { // Checking response content resContent := string(resBytes) require.Contains(t, resContent, "inputToken.name = 'wresult'") - require.Contains(t, resContent, "form.action = \"ms-app://windows.immersivecontrolpanel\"") + // we expect the URL to be escaped + require.Contains(t, resContent, `form.action = "ms-app:\/\/windows.immersivecontrolpanel"`) require.Contains(t, resContent, "performPost()") // Getting token content @@ -7939,6 +7940,79 @@ func (s *integrationMDMTestSuite) TestInvalidGetAuthRequest() { require.Contains(t, resContent, "forbidden") } +func (s *integrationMDMTestSuite) TestAppruValidationInGetAuthRequest() { + t := s.T() + + // Test cases with invalid appru values that should bail due to exiting before auth check + invalidAppruCases := []struct { + name string + appru string + }{ + { + name: "javascript injection", + appru: "%3Bfor%20(var%20key%20in%20localStorage)%7B%20alert(key)%7D%3B%2F%2F", + }, + { + name: "javascript protocol", + appru: "javascript:alert(1)", + }, + { + name: "data URI", + appru: "data:text/html,<script>alert(1)</script>", + }, + { + name: "empty scheme", + appru: "://example.com", + }, + { + name: "plain text", + appru: "not-a-url", + }, + } + + for _, tc := range invalidAppruCases { + t.Run(tc.name, func(t *testing.T) { + targetEndpointURL := microsoft_mdm.MDE2AuthPath + "?appru=" + tc.appru + "&login_hint=demo%40example.com" + resp := s.DoRaw("GET", targetEndpointURL, nil, http.StatusInternalServerError) + + resBytes, err := io.ReadAll(resp.Body) + resContent := string(resBytes) + require.NoError(t, err) + require.NotEmpty(t, resBytes) + require.Contains(t, resContent, "forbidden") + + resp.Body.Close() + }) + } + + // Also verify valid URLs still work + validAppruCases := []struct { + name string + appru string + }{ + { + name: "ms-app scheme", + appru: "ms-app%3A%2F%2Fwindows.immersivecontrolpanel", + }, + { + name: "https scheme", + appru: "https%3A%2F%2Fexample.com%2Fcallback", + }, + { + name: "http scheme", + appru: "http%3A%2F%2Flocalhost%2Fcallback", + }, + } + + for _, tc := range validAppruCases { + t.Run(tc.name, func(t *testing.T) { + targetEndpointURL := microsoft_mdm.MDE2AuthPath + "?appru=" + tc.appru + "&login_hint=demo%40example.com" + resp := s.DoRaw("GET", targetEndpointURL, nil, http.StatusOK) + resp.Body.Close() + }) + } +} + func (s *integrationMDMTestSuite) TestValidGetTOC() { t := s.T()
server/service/microsoft_mdm.go+19 −2 modified@@ -11,13 +11,14 @@ import ( "errors" "fmt" "html" + "html/template" "io" "net" "net/http" "net/url" + "slices" "strconv" "strings" - "text/template" "time" "github.com/fleetdm/fleet/v4/pkg/fleetdbase" @@ -409,7 +410,7 @@ func NewSoapFault(errorType string, origMessage int, errorMessage error) mdm_typ } } -// getSTSAuthContent Retuns STS auth content +// getSTSAuthContent Returns STS auth content func getSTSAuthContent(data string) mdm_types.Errorer { return MDMAuthContainer{ Data: &data, @@ -777,6 +778,17 @@ func mdmMicrosoftDiscoveryEndpoint(ctx context.Context, request interface{}, svc }, nil } +// isValidAppru validates that appru is a valid URL with an allowed scheme. +// It returns true if appru is a valid URL with http, https, or ms-app scheme. +func isValidAppru(appru string) bool { + parsed, err := url.Parse(appru) + if err != nil { + return false + } + + return slices.Contains([]string{"http", "https", "ms-app"}, parsed.Scheme) +} + // mdmMicrosoftAuthEndpoint handles the Security Token Service (STS) implementation func mdmMicrosoftAuthEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (mdm_types.Errorer, error) { params := request.(*SoapRequestContainer).Params @@ -793,6 +805,11 @@ func mdmMicrosoftAuthEndpoint(ctx context.Context, request interface{}, svc flee return getSTSAuthContent(""), errors.New("expected STS params are empty") } + // Validate that appru is a valid URL + if !isValidAppru(appru) { + return getSTSAuthContent(""), fmt.Errorf("non-URL appru parameter attempted: %q", appru) + } + // Getting the STS endpoint HTML content stsAuthContent, err := svc.GetMDMMicrosoftSTSAuthResponse(ctx, appru, loginHint) if err != nil {
server/service/microsoft_mdm_test.go+74 −0 modified@@ -14,6 +14,7 @@ import ( "github.com/fleetdm/fleet/v4/server/mdm/microsoft/syncml" "github.com/fleetdm/fleet/v4/server/mock" "github.com/go-kit/log" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -35,6 +36,79 @@ func NewSoapRequest(request []byte) (fleet.SoapRequest, error) { return req, nil } +func TestIsValidAppruURL(t *testing.T) { + tests := []struct { + name string + appru string + expected bool + }{ + // Valid URLs + { + name: "valid ms-app scheme", + appru: "ms-app://windows.immersivecontrolpanel", + expected: true, + }, + { + name: "valid https scheme", + appru: "https://example.com/callback", + expected: true, + }, + { + name: "valid http scheme", + appru: "http://localhost/callback", + expected: true, + }, + // Invalid URLs - XSS attempts + { + name: "javascript injection", + appru: ";for (var key in localStorage){ alert(key)};//", + expected: false, + }, + { + name: "javascript protocol", + appru: "javascript:alert(1)", + expected: false, + }, + { + name: "data URI", + appru: "data:text/html,<script>alert(1)</script>", + expected: false, + }, + { + name: "empty scheme", + appru: "://example.com", + expected: false, + }, + { + name: "plain text", + appru: "not-a-url", + expected: false, + }, + { + name: "empty string", + appru: "", + expected: false, + }, + { + name: "file scheme", + appru: "file:///etc/passwd", + expected: false, + }, + { + name: "ftp scheme", + appru: "ftp://example.com", + expected: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := isValidAppru(tc.appru) + assert.Equal(t, tc.expected, result) + }) + } +} + func TestValidSoapResponse(t *testing.T) { relatesTo := "urn:uuid:0d5a1441-5891-453b-becf-a2e5f6ea3749" soapFaultMsg := NewSoapFault(syncml.SoapErrorAuthentication, fleet.MDEDiscovery, errors.New("test"))
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- github.com/advisories/GHSA-gfpw-jgvr-cw4jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-22808ghsaADVISORY
- github.com/fleetdm/fleet/commit/0e6c790803d1b4407c5b4b41a67a37864a3d3573ghsaWEB
- github.com/fleetdm/fleet/security/advisories/GHSA-gfpw-jgvr-cw4jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.