Jan 20, 2026·6 min read·80 visits
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.
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 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.
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."
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:
HS256 (or any valid alg string, honestly) and throw in a random Key ID (kid).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).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 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 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.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L| 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 |
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.
A collection of multiple security issues in Etherpad before version 3.3.0, involving weak token generation, timing side channels, API parameter pollution, path traversal, and file-system path disclosure.
An algorithmic complexity vulnerability in the python-sqlparse library allows remote, unauthenticated attackers to cause a Denial of Service (DoS) via resource exhaustion. By transmitting a carefully constructed SQL statement containing deeply nested structures, an attacker can trigger quadratic CPU consumption within the parsing engine. This behavior bypasses the built-in depth limits because the performance degradation occurs during the initial recursive tree construction, causing the application process to hang.
A critical vulnerability exists in the atomic-agents-stack package up to version 1.0.0. The HTTP Model Context Protocol (MCP) server-registry backend factory retrieves catalog metadata over cleartext HTTP by default. Because these catalogs define execution parameters ('command' and 'args') for local stdio subprocesses, a network-positioned attacker can intercept the cleartext traffic and inject arbitrary commands. This results in arbitrary remote code execution on the agent host system without requiring user interaction.
A high-severity vulnerability in the atomic-agents-stack framework allows complete bypass of cost-cap guardrails during parallel model execution when utilizing unlisted, local, or self-hosted models.
A security vulnerability in the Go library software.sslmate.com/src/go-pkcs12 allows remote attackers to bypass password-based integrity verification. By crafting a PKCS#12 file with an excessively short KeyLength parameter in the PBMAC1 configuration, the derived MAC key space collapses, allowing an attacker to forge arbitrary certificate structures and private keys that are incorrectly verified as valid.
A workspace boundary bypass vulnerability exists in the Chrome DevTools for Agents (chrome-devtools-mcp) Model Context Protocol (MCP) server from version 0.24.0 up to 1.1.0. The vulnerability allows an agent or malicious workspace containing symbolic links to read or modify arbitrary files outside the configured project workspace root directory. This occurs because the path validation function resolves paths lexically rather than physically.