Fleet Fiasco: The Unverified JWT That Opened the Gates
Jan 20, 2026·6 min read·10 visits
Executive Summary (TL;DR)
Fleet DM improperly handled Azure AD tokens during Windows device enrollment. Instead of verifying cryptographic signatures, the server blindly accepted the claims inside the token using `ParseUnverified`. This allows an attacker to craft a fake token, impersonate any user or tenant, and enroll unauthorized devices into the management fleet without valid credentials.
A critical authentication bypass in Fleet Device Management's Windows MDM enrollment flow allows attackers to spoof Azure AD identities by submitting unsigned or maliciously crafted JWTs.
The Hook: Trust Me, Bro
In the world of Mobile Device Management (MDM), trust is everything. You install agents on your employees' laptops to ensure compliance, push updates, and monitor security. The server must know that the device asking to join the party is actually allowed to be there. Fleet Device Management (Fleet DM), a popular open-source platform powered by osquery, handles this for thousands of endpoints.
But what happens when the bouncer at the door stops checking IDs and just glances at the name tag? That's essentially what happened here. In the Windows MDM enrollment flow—specifically where the server talks to Azure Active Directory (Azure AD)—Fleet implemented a check that was functionally equivalent to a 'Trust Me, Bro' handshake.
Instead of cryptographically verifying that Microsoft actually signed the authentication token, Fleet just read the JSON and assumed it was telling the truth. This vulnerability turns the heavily fortified front door of your MDM into a revolving door for anyone who knows how to edit a JSON file.
The Flaw: A fatal `ParseUnverified`
The root cause lies in server/mdm/microsoft/wstep.go. When a Windows device tries to enroll via the MDE2EnrollPath, it presents an Azure AD token to prove its identity. The Fleet server needs to validate this token. The correct way to do this is to fetch Microsoft's public keys (JWKS) and verify the signature.
The incorrect way—and the way Fleet did it—was to use the method ParseUnverified from the golang-jwt library. As the name implies, this function explicitly skips signature verification. It takes the base64-encoded blob, decodes it, and hands you the data.
It is a common developer trap: you just want to read the User Principal Name (UPN) or Tenant ID to log who is connecting, so you reach for the easiest parsing function. But if you rely on that data for authentication decision-making—which Fleet did—you have created a catastrophic authentication bypass. The server wasn't checking if the token was valid; it was just checking what the token claimed to be.
The Code: The Smoking Gun
Let's look at the vulnerable code. It's short, simple, and deadly. In the GetAzureAuthTokenClaims function, the developers did this:
// THE BAD CODE
token, _, err := new(jwt.Parser).ParseUnverified(string(tokenBytes), jwt.MapClaims{})
if err != nil {
return nil, fmt.Errorf("parsing token: %w", err)
}
// Proceed to trust claims inside 'token'There is no cryptographic math happening here. It is just deserialization. If I send a token signed by "Bob's Discount Certificates" or even a token with "alg": "none", this code accepts it without complaint.
The fix, applied in commit e225ef57912c8f4ac8977e24b5ebe1d9fd875257, introduces reality back into the equation. It sets up a proper JWKS client to talk to Microsoft and enforces signature validation:
// THE FIX
// 1. Setup keyset to fetch from Microsoft
keys, err := jwkset.NewDefaultHTTPClient([]string{"https://login.microsoftonline.com/common/discovery/v2.0/keys"})
// 2. Parse WITH verification
token, err := jwt.Parse(string(tokenBytes), func(token *jwt.Token) (interface{}, error) {
// 3. Verify the key ID (kid) exists in Microsoft's keyset
kidStr, ok := token.Header["kid"].(string)
if !ok {
return nil, fmt.Errorf("jwt header missing kid")
}
// 4. Return the actual public key for verification
key, err := keys.KeyRead(ctx, kidStr)
return key.Key(), nil
})This change shifts the logic from "read whatever is sent" to "cryptographically prove Microsoft signed this."
The Exploit: Crafting the Golden Ticket
Exploiting this requires zero fancy tools. You don't need to steal a private key because the server ignores the signature anyway. You just need a script to generate a JWT.
Here is the recipe for disaster:
- Header: Set the algorithm to
HS256(or any valid alg string, honestly) and throw in a random Key ID (kid). - Payload: This is where the magic happens. You need to mimic the claims Fleet expects.
upn:admin@victim-corp.com(Target the admin).tid: The victim's Azure Tenant ID (often public or easy to find).scp:mdm_delegation(This is critical—it tells Fleet this token is for MDM enrollment).
- Signature: Sign it with the string "password123" or
null. It doesn't matter. The server won't check.
Once constructed, the attacker sends this token to the enrollment endpoint. The Fleet server parses the upn and tid, decides "Oh, hello Admin," and initiates the enrollment process. The attacker can now register a rogue device into the fleet, potentially receiving sensitive configuration profiles, certificates, or wifi credentials that are pushed to new devices.
The Impact: Why Should We Panic?
The impact here is High on Confidentiality and Integrity. By enrolling a rogue device, an attacker gains a foothold in the corporate environment that is sanctioned by the management software itself.
First, Identity Impersonation: The attacker effectively becomes any user they choose. If Fleet uses these claims to assign device ownership, the attacker can masquerade as a C-level executive.
Second, Config Extraction: MDM solutions often push sensitive data to devices immediately upon enrollment—VPN certificates, Wi-Fi passwords, and software license keys. An attacker simply has to enroll a virtual machine, wait for the policy sync, and harvest these secrets.
Finally, Policy Manipulation: Depending on how Fleet is configured, if an attacker can impersonate an admin account, they might be able to influence the state of other devices or bypass conditional access policies that rely on device health attestation.
The Fix: Verification is Not Optional
The remediation is straightforward but mandatory: Update Fleet immediately. The patch forces the server to validate tokens against Microsoft's official OpenID Connect discovery endpoint.
If you are a developer reading this, let this be a lesson: Never use ParseUnverified (or decode_jwt without verification) unless you are 100% certain you only need the data for debugging or display purposes after the signature has already been validated elsewhere. If your application makes a security decision based on a JWT, that JWT must be signed, and you must check the signature.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:LAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Fleet Fleet Device Management | < Jan 2026 Patch | Commit e225ef5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-347 |
| Attack Vector | Network |
| CVSS | 8.8 (High) |
| Impact | Authentication Bypass / Identity Spoofing |
| Component | server/mdm/microsoft/wstep.go |
| Exploit Status | PoC Available |
MITRE ATT&CK Mapping
The product does not verify, or incorrectly verifies, the cryptographic signature for data, allowing an attacker to modify the data or provide a fake source.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.