Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 8.6.0
current_version = 8.6.1
commit = True
tag = True
tag_name = {new_version}
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ Authors in order of the timeline of their contributions:
- [dtorres-sf](https://github.com/dtorres-sf) for the fix for moving nested tables when using iterable_compare_func.
- [Jim Cipar](https://github.com/jcipar) for the fix recursion depth limit when hashing numpy.datetime64
- [Enji Cooper](https://github.com/ngie-eign) for converting legacy setuptools use to pyproject.toml
- [Diogo Correia](https://github.com/diogotcorreia) for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# DeepDiff Change log

- v8-6-1
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).


- v8-6-0
- Added Colored View thanks to @mauvilsa
- Added support for applying deltas to NamedTuple thanks to @paulsc
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ authors:
given-names: "Sep"
orcid: "https://orcid.org/0009-0009-5828-4345"
title: "DeepDiff"
version: 8.6.0
version: 8.6.1
date-released: 2024
url: "https://github.com/seperman/deepdiff"
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DeepDiff v 8.6.0
# DeepDiff v 8.6.1

![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat)
![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat)
Expand All @@ -17,12 +17,15 @@

Tested on Python 3.9+ and PyPy3.

- **[Documentation](https://zepworks.com/deepdiff/8.6.0/)**
- **[Documentation](https://zepworks.com/deepdiff/8.6.1/)**

## What is new?

Please check the [ChangeLog](CHANGELOG.md) file for the detailed information.

DeepDiff 8-6-1
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).

DeepDiff 8-6-0

- Added Colored View thanks to @mauvilsa
Expand Down
2 changes: 1 addition & 1 deletion deepdiff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes."""
# flake8: noqa
__version__ = '8.6.0'
__version__ = '8.6.1'
import logging

if __name__ == '__main__':
Expand Down
8 changes: 7 additions & 1 deletion deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)
from deepdiff.path import (
_path_to_elements, _get_nested_obj, _get_nested_obj_and_force,
GET, GETATTR, parse_path, stringify_path,
GET, GETATTR, check_elem, parse_path, stringify_path,
)
from deepdiff.anyset import AnySet
from deepdiff.summarize import summarize
Expand Down Expand Up @@ -237,6 +237,11 @@ def _get_elem_and_compare_to_old_value(
forced_old_value=None,
next_element=None,
):
try:
check_elem(elem)
except ValueError as error:
self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path_for_err_reporting, error))
return not_found
# if forced_old_value is not None:
try:
if action == GET:
Expand Down Expand Up @@ -536,6 +541,7 @@ def _get_elements_and_details(self, path):
obj = self
# obj = self.get_nested_obj(obj=self, elements=elements[:-1])
elem, action = elements[-1] # type: ignore
check_elem(elem)
except Exception as e:
self._raise_or_log(UNABLE_TO_GET_ITEM_MSG.format(path, e))
return None
Expand Down
7 changes: 7 additions & 0 deletions deepdiff/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def _path_to_elements(path, root_element=DEFAULT_FIRST_ELEMENT):

def _get_nested_obj(obj, elements, next_element=None):
for (elem, action) in elements:
check_elem(elem)
if action == GET:
obj = obj[elem]
elif action == GETATTR:
Expand All @@ -134,11 +135,17 @@ def _guess_type(elements, elem, index, next_element):
return {}


def check_elem(elem):
if isinstance(elem, str) and elem.startswith("__") and elem.endswith("__"):
raise ValueError("traversing dunder attributes is not allowed")


def _get_nested_obj_and_force(obj, elements, next_element=None):
prev_elem = None
prev_action = None
prev_obj = obj
for index, (elem, action) in enumerate(elements):
check_elem(elem)
_prev_obj = obj
if action == GET:
try:
Expand Down
4 changes: 2 additions & 2 deletions deepdiff/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class UnsupportedFormatErr(TypeError):
DELTA_IGNORE_ORDER_NEEDS_REPETITION_REPORT = 'report_repetition must be set to True when ignore_order is True to create the delta object.'
DELTA_ERROR_WHEN_GROUP_BY = 'Delta can not be made when group_by is used since the structure of data is modified from the original form.'

SAFE_TO_IMPORT = {
SAFE_TO_IMPORT = frozenset({
'builtins.range',
'builtins.complex',
'builtins.set',
Expand Down Expand Up @@ -95,7 +95,7 @@ class UnsupportedFormatErr(TypeError):
'ipaddress.IPv4Address',
'ipaddress.IPv6Address',
'collections.abc.KeysView',
}
})


TYPE_STR_TO_TYPE = {
Expand Down
1 change: 1 addition & 0 deletions docs/authors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ and polars support.
limit when hashing numpy.datetime64
- `Enji Cooper <https://github.com/ngie-eign>`__ for converting legacy
setuptools use to pyproject.toml
- `Diogo Correia <https://github.com/diogotcorreia>`__ for reporting security vulnerability in Delta and DeepDiff that could allow remote code execution.


.. _Sep Dehpour (Seperman): http://www.zepworks.com
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Changelog

DeepDiff Changelog

- v8-6-1
- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).

- v8-6-0
- Added Colored View thanks to @mauvilsa
- Added support for applying deltas to NamedTuple thanks to @paulsc
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@
# built documents.
#
# The short X.Y version.
version = '8.6.0'
version = '8.6.1'
# The full version, including alpha/beta/rc tags.
release = '8.6.0'
release = '8.6.1'

load_dotenv(override=True)
DOC_VERSION = os.environ.get('DOC_VERSION', version)
Expand Down
8 changes: 7 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
contain the root `toctree` directive.
DeepDiff 8.6.0 documentation!
DeepDiff 8.6.1 documentation!
=============================

*******
Expand All @@ -31,6 +31,12 @@ The DeepDiff library includes the following modules:
What Is New
***********

DeepDiff 8-6-1
--------------

- Patched security vulnerability in the Delta class which was vulnerable to class pollution via its constructor, and when combined with a gadget available in DeltaDiff itself, it could lead to Denial of Service and Remote Code Execution (via insecure Pickle deserialization).


DeepDiff 8-6-0
--------------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "deepdiff"
version = "8.6.0"
version = "8.6.1"
dependencies = [
"orderly-set>=5.4.1,<6",
]
Expand Down
133 changes: 133 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import os
import pickle
import pytest
from deepdiff import Delta
from deepdiff.helper import Opcode
from deepdiff.serialization import ForbiddenModule


class TestDeltaClassPollution:

def test_builtins_int(self):

pollute_int = pickle.dumps(
{
"values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
"dictionary_item_added": {
(
("root", "GETATTR"),
("tmp", "GET"),
("__repr__", "GETATTR"),
("__globals__", "GETATTR"),
("__builtins__", "GET"),
("int", "GET"),
): "no longer a class"
},
}
)

assert isinstance(pollute_int, bytes)

# ------------[ Exploit ]------------
# This could be some example, vulnerable, application.
# The inputs above could be sent via HTTP, for example.


# Existing dictionary; it is assumed that it contains
# at least one entry, otherwise a different Delta needs to be
# applied first, adding an entry to the dictionary.
mydict = {"tmp": "foobar"}

# Before pollution
assert 42 == int("41") + 1

# Apply Delta to mydict
result = mydict + Delta(pollute_int)

assert 1337 == int("1337")

def test_remote_code_execution(self):
if os.path.exists('/tmp/pwned'):
os.remove('/tmp/pwned')

pollute_safe_to_import = pickle.dumps(
{
"values_changed": {"root['tmp']": {"new_value": Opcode("", 0, 0, 0, 0)}},
"set_item_added": {
(
("root", "GETATTR"),
("tmp", "GET"),
("__repr__", "GETATTR"),
("__globals__", "GETATTR"),
("sys", "GET"),
("modules", "GETATTR"),
("deepdiff.serialization", "GET"),
("SAFE_TO_IMPORT", "GETATTR"),
): set(["posix.system"])
},
}
)

# From https://davidhamann.de/2020/04/05/exploiting-python-pickle/
class RCE:
def __reduce__(self):
cmd = "id > /tmp/pwned"
return os.system, (cmd,)

# Wrap object with dictionary so that Delta does not crash
rce_pickle = pickle.dumps({"_": RCE()})

assert isinstance(pollute_safe_to_import, bytes)
assert isinstance(rce_pickle, bytes)

# ------------[ Exploit ]------------
# This could be some example, vulnerable, application.
# The inputs above could be sent via HTTP, for example.

# Existing dictionary; it is assumed that it contains
# at least one entry, otherwise a different Delta needs to be
# applied first, adding an entry to the dictionary.
mydict = {"tmp": "foobar"}

# Apply Delta to mydict
with pytest.raises(ValueError) as exc_info:
mydict + Delta(pollute_safe_to_import)
assert "traversing dunder attributes is not allowed" == str(exc_info.value)

with pytest.raises(ForbiddenModule) as exc_info:
Delta(rce_pickle) # no need to apply this Delta
assert "Module 'posix.system' is forbidden. You need to explicitly pass it by passing a safe_to_import parameter" == str(exc_info.value)

assert not os.path.exists('/tmp/pwned'), "We should not have created this file"

def test_delta_should_not_access_globals(self):

pollute_global = pickle.dumps(
{
"dictionary_item_added": {
(
("root", "GETATTR"),
("myfunc", "GETATTR"),
("__globals__", "GETATTR"),
("PWNED", "GET"),
): 1337
}
}
)


# demo application
class Foo:
def __init__(self):
pass

def myfunc(self):
pass


PWNED = False
delta = Delta(pollute_global)
assert PWNED is False
b = Foo() + delta

assert PWNED is False
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.