제출 #844900: FederatedAI FATE (OSX Broker) 5a06d9e4c4cd7ab97a5c8357828adbffaca87785 Improper Isolation / Cross-Tenant Data Exposure정보

제목FederatedAI FATE (OSX Broker) 5a06d9e4c4cd7ab97a5c8357828adbffaca87785 Improper Isolation / Cross-Tenant Data Exposure
설명## Vulnerability Title Cross-Tenant Data Exposure via Hash-Key Confusion in OSX Broker Eggroll Session Lookup ## Affected Component `java/osx/osx-broker/src/main/java/org/fedai/osx/broker/grpc/QueuePushReqStreamObserver.java` Repository: https://github.com/FederatedAI/FATE ## Summary An attacker with the ability to submit OSX push requests can craft a `RollSiteHeader` that reuses another job's computed Eggroll session key. This causes session-cache key reuse and Eggroll cluster-manager lookup by the same `SessionMeta.id`, leading to cross-session data contamination, unauthorized session reuse, or resource abuse affecting FATE deployments using the OSX broker with Eggroll. ## Technical Details The vulnerability occurs because the OSX broker computes an Eggroll session lookup key from only `rollSiteSessionId`, `dstRole`, and `dstPartyId`, then uses the resulting string as both the local session-cache key and the `SessionMeta.id` sent to the Eggroll cluster manager. This key excludes critical fields such as `srcRole`, `srcPartyId`, `tenant_id`, and `user_id`. **Where the Identity Key is Computed** In `QueuePushReqStreamObserver.java`, the session key is computed via simple string concatenation using underscore separators: ```java String rsKey = rsHeader.getRsKey("#", "__rsk"); String sessionId = String.join("_", rsHeader.getRollSiteSessionId(), rsHeader.getDstRole(), rsHeader.getDstPartyId()); context.setSessionId(sessionId); ErSession session = null; try { session = PutBatchSinkUtil.sessionCache.get(sessionId); } // ... ``` **Vulnerable Session Lookup** The computed key is used as the local cache key for `ErSession` objects (`PutBatchSinkUtil.sessionCache.get()`). When a cache miss occurs, the same string is used as the `ErSessionMeta.id` for the Eggroll cluster manager lookup: ```java ErSessionMeta erSessionMetaArgs = new ErSessionMeta(); erSessionMetaArgs.setId(sessionId); // ... erSessionMeta = clusterManagerClient.getSession(erSessionMetaArgs); ``` Because `srcRole`, `srcPartyId`, and authenticated owner identities are excluded, two requests from entirely different users or source parties can be treated as the same backend session if they target the same destination and share a `rollSiteSessionId`. **How the Attacker Constructs a Conflicting Object** Victim request: ```json { "tenant_id": "tenant_alpha", "user_id": "alice", "job_id": "job-train-1", "rollSiteSessionId": "sess-42", "srcRole": "guest", "srcPartyId": "9999", "dstRole": "host", "dstPartyId": "10000" } ``` Attacker request: ```json { "tenant_id": "tenant_beta", "user_id": "bob", "job_id": "job-train-9", "rollSiteSessionId": "sess-42", "srcRole": "guest", "srcPartyId": "1234", "dstRole": "host", "dstPartyId": "10000" } ``` Both requests compute the exact same session key: `sess-42_host_10000`. The broker session lookup cannot distinguish between the two, despite differing security and tenant contexts. ## Impact This vulnerability allows attackers to: - Reuse an Eggroll session associated with another security context by submitting a colliding `RollSiteHeader`. - Poison or interfere with another job's RollPair/table operations by sending data through the shared backend session identity. - Abuse already allocated Eggroll processors or cause a Denial of Service against the victim's session. *(Note: While local `ErSession` cache entries expire after 10 seconds, backend Eggroll sessions identified by the vulnerable `SessionMeta.id` may remain reusable until stopped).* ## Proof of Concept The following script demonstrates the application-level key confusion, proving that two non-equivalent request objects produce the same session key and reuse the same cache object. ```python #!/usr/bin/env python3 def vulnerable_session_key(header): return "_".join([ header["rollSiteSessionId"], header["dstRole"], header["dstPartyId"], ]) session_cache = {} cluster_manager_lookups = [] def handle_push(request): key = vulnerable_session_key(request) if key not in session_cache: session_cache[key] = { "SessionMeta.id": key, "first_actor": request["actor"], } cluster_manager_lookups.append({"SessionMeta.id": key}) return key, session_cache[key] victim = { "actor": "victim Alice", "tenant_id": "tenant_alpha", "user_id": "alice", "job_id": "job-train-1", "rollSiteSessionId": "sess-42", "srcRole": "guest", "srcPartyId": "9999", "dstRole": "host", "dstPartyId": "10000", } attacker = { "actor": "attacker Bob", "tenant_id": "tenant_beta", "user_id": "bob", "job_id": "job-train-9", "rollSiteSessionId": "sess-42", "srcRole": "guest", "srcPartyId": "1234", "dstRole": "host", "dstPartyId": "10000", } victim_key, victim_session = handle_push(victim) attacker_key, attacker_session = handle_push(attacker) print("victim key: ", victim_key) print("attacker key:", attacker_key) print("same cache object:", victim_session is attacker_session) print("cluster manager lookups:", cluster_manager_lookups) ``` *Observed execution output confirms `victim_key` matches `attacker_key` (`sess-42_host_10000`) and `same cache object: True`, proving cross-tenant reuse.* ## Remediation Bind the Eggroll session lookup to the full security context or validate ownership before session reuse. Example implementation direction: ```java // In QueuePushReqStreamObserver.initEggroll // Derive or validate a canonical session identity that includes the authenticated caller, source party, tenant/owner, and a key schema version. String sessionId = buildCanonicalSessionIdentity(authenticatedContext, rsHeader); validateSessionOwner(authenticatedContext, sessionId, rsHeader); ErSession session = PutBatchSinkUtil.sessionCache.get(sessionId); ``` Additional mitigations: 1. Complete Field Coverage: Include `srcRole`, `srcPartyId`, and authenticated tenant/user IDs in the backend session identity. 2. Read-Time Revalidation: Re-check session ownership before returning or reusing cached `ErSession` objects. 3. Domain Separation: Separate session namespaces strictly across tenants, users, and source parties. ## References - Vulnerable session key construction: `https://github.com/FederatedAI/FATE/blob/5a06d9e4c4cd7ab97a5c8357828adbffaca87785/java/osx/osx-broker/src/main/java/org/fedai/osx/broker/grpc/QueuePushReqStreamObserver.java#L184-L193` - Session cache mechanism: `https://github.com/FederatedAI/FATE/blob/5a06d9e4c4cd7ab97a5c8357828adbffaca87785/java/osx/osx-broker/src/main/java/org/fedai/osx/broker/eggroll/PutBatchSinkUtil.java#L24-L38` - Cluster-manager lookup: `https://github.com/FederatedAI/FATE/blob/5a06d9e4c4cd7ab97a5c8357828adbffaca87785/java/osx/osx-broker/src/main/java/org/fedai/osx/broker/eggroll/ClusterManagerClient.java#L69-L75`
원천⚠️ https://github.com/FederatedAI/FATE/issues/5791
사용자
 Dem00000 (UID 98563)
제출2026. 06. 01. PM 03:19 (1 월 ago)
모더레이션2026. 07. 03. PM 06:52 (1 month later)
상태수락
VulDB 항목376137 [FederatedAI FATE 까지 2.2.0 OSX Broker QueuePushReqStreamObserver.java QueuePushReqStreamObserver.initEggroll rollSiteSessionId/dstRole/dstPartyId 정보 공개]
포인트들20

Want to know what is going to be exploited?

We predict KEV entries!