CVE-2023-26145 (published 2023-09-28T05:15Z)

This affects versions of the package pydash before 6.0.0. A number of pydash methods such as pydash.objects.invoke() and pydash.collections.invoke_map() accept dotted paths (Deep Path Strings) to target a nested Python object, relative to the original source object. These paths can be used to target internal class attributes and dict items, to retrieve, modify or invoke nested Python objects.\r\r**Note:**\r\rThe pydash.objects.invoke() method is vulnerable to Command Injection when the following prerequisites are satisfied:\r\r1) The source object (argument 1) is not a built-in object such as list/dict (otherwise, the __init__.__globals__ path is not accessible)\r\r2) The attacker has control over argument 2 (the path string) and argument 3 (the argument to pass to the invoked method)\r\r\rThe pydash.collections.invoke_map() method is also vulnerable, but is harder to exploit as the attacker does not have direct control over the argument to be passed to the invoked function.

CVSS v3 base score: 8.1, HIGH (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         
cwe_id: CWE-77
cwe_name: Improper Neutralization of Special Elements used in a Command ('Command Injection')
description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
extended_description: 
            Many protocols and products have their own custom command language. While OS or shell command strings are frequently discovered and targeted, developers may not realize that these other command languages might also be vulnerable to attacks.
         
         

Fix information:
repo_url: https://github.com/dgilland/pydash
commit_details: repo_url: https://github.com/dgilland/pydash
author: Derrick Gilland
author_date: 2023-01-28 20:30:52-05:00
msg: fix: don't allow object paths that reference dunder-method attributes for functions like get()
repository: repo_url: https://github.com/dgilland/pydash
repo_name: dgilland/pydash
description: The kitchen sink of Python utility libraries for doing "stuff" in a functional way. Based on the Lo-Dash Javascript library.
repo_language: Python
owner: dgilland
file_changes: filename: helpers.py
change_type: ModificationType.MODIFY
diff: @@ -178,6 +178,7 @@ def _base_get_item(obj, key, default=UNSET):
 def _base_get_object(obj, key, default=UNSET):
     value = _base_get_item(obj, key, default=UNSET)
     if value is UNSET:
+        _raise_if_restricted_key(key)
         value = default
         try:
             value = getattr(obj, key)
@@ -186,6 +187,13 @@ def _base_get_object(obj, key, default=UNSET):
     return value
 
 
+def _raise_if_restricted_key(key):
+    # Prevent access to dunder-methods since this could expose access to globals through leaky
+    # attributes such as obj.__init__.__globals__.
+    if len(key) > 4 and key.isascii() and key.startswith("__") and key.endswith("__"):
+        raise KeyError(f"access to restricted key {key!r} is not allowed")
+
+
 def base_set(obj, key, value, allow_override=True):
     """
     Set an object's `key` to `value`. If `obj` is a ``list`` and the `key` is the next available
@@ -213,6 +221,7 @@ def base_set(obj, key, value, allow_override=True):
                 obj[:] = (obj + [None] * key)[:key]
             obj.append(value)
     elif (allow_override or not hasattr(obj, key)) and obj is not None:
+        _raise_if_restricted_key(key)
         setattr(obj, key, value)
 
     return obj

programming_language: Python
filename: test_objects.py
change_type: ModificationType.MODIFY
diff: @@ -380,6 +380,30 @@ def test_get__should_not_populate_defaultdict():
     assert data == {}
 
 
+@parametrize(
+    "obj,path",
+    [
+        (helpers.Object(), "__init__"),
+        (helpers.Object(subobj=helpers.Object()), "subobj.__init__"),
+        (namedtuple("a", ["a"])(a=1), "__len__"),
+    ],
+)
+def test_get__raises_for_objects_when_path_restricted(obj, path):
+    with pytest.raises(KeyError, match="access to restricted key"):
+        _.get(obj, path)
+
+
+@parametrize(
+    "obj,path",
+    [
+        ({}, "__init__"),
+        ([], "__contains__"),
+    ],
+)
+def test_get__does_not_raise_for_dict_or_list_when_path_restricted(obj, path):
+    assert _.get(obj, path) is None
+
+
 @parametrize(
     "case,expected",
     [

programming_language: Python