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

Fleet Windows MDM endpoint has a Cross-site Scripting vulnerability

CVE-2026-22808

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.

PackageAffected versionsPatched versions
github.com/fleetdm/fleetGo
>= 4.78.0, < 4.78.24.78.2
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/fleet/v4Go
< 4.43.5-0.20260111020427-0e6c790803d14.43.5-0.20260111020427-0e6c790803d1

Affected products

1
  • Range: fleet-v4.76.0, fleet-v4.76.1, fleet-v4.77.0, …

Patches

1
0e6c790803d1

Add additional validation to mdmMicrosoftAuthEndpoint (#38147)

https://github.com/fleetdm/fleetIan LittmanJan 11, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.