CVE-2026-41262
published 2026-06-26CVE-2026-41262: Fleet DM Vulnerable to Cross-Team Policy Data Exposure via Global Policy Read Endpoint ## Summary The global policy read endpoint (`GET…
medium
Fleet DM Vulnerable to Cross-Team Policy Data Exposure via Global Policy Read Endpoint
## Summary
The global policy read endpoint (`GET /api/latest/fleet/policies/{policy_id}`) performs authorization against an empty `fleet.Policy{}` struct with nil TeamID, then fetches any policy by ID from the database without verifying the fetched policy actually belongs to the global scope. This allows a user with observer-level access on any single team to read the full details of policies belonging to any other team, bypassing Fleet's team isolation model.
## Details
The vulnerability is in `GetPolicyByIDQueries` at `server/service/global_policies.go:163-180`:
```go
func (svc Service) GetPolicyByIDQueries(ctx context.Context, policyID uint) (*fleet.Policy, error) {
// Auth check uses empty Policy{} — TeamID is nil
if err := svc.authz.Authorize(ctx, &fleet.Policy{}, fleet.ActionRead); err != nil {
return nil, err
}
// Fetches ANY policy by ID, regardless of team ownership
policy, err := svc.ds.Policy(ctx, policyID)
if err != nil {
return nil, err
}
// ... populates install_software and run_script, returns full policy
return policy, nil
}
```
The authorization passes because the OPA rule at `server/authz/policy.rego:724-728` allows reading policies with null `team_id` for any user who holds a role on any team:
```rego
allow {
is_null(object.team_id)
object.type == "policy"
team_role(subject, subject.teams[_].id) == [admin, maintainer, technician, observer, observer_plus][_]
action == read
}
```
Since the auth object has nil TeamID, this rule fires for any team member. After authorization, `ds.Policy()` calls `policyDB()` at `server/datastore/mysql/policies.go:283-288` with a nil teamID:
```go
func policyDB(ctx context.Context, q sqlx.QueryerContext, id uint, teamID *uint) (*fleet.Policy, error) {
teamWhere := "TRUE" // nil teamID → no team filter
args := []interface{}{id}
if teamID != nil {
teamWhere = "team_id = ?"
args = append(args, *teamID)
}
// ... executes SELECTAffected
1 ranges
| Vendor | Product | Version range | Fixed in |
|---|---|---|---|
| github.com | fleetdm_fleet_v4 | >= 0 < 4.85.0 | 4.85.0 |
Stop checking back — get the weekly exploitation signal.
Every Monday: what got weaponized or added to CISA KEV in the last seven days — each CVE cross-linked to its PoC, Nuclei template, and detection rule. Free, one email a week, unsubscribe in one click.
No detection rules found.
No public exploits indexed.
No writeups or analysis indexed.
2026-06-26
Published