CVE-2023-6395: Insufficient Information (NVD-CWE-noinfo)
value: The Mock software contains a vulnerability wherein an attacker could potentially exploit privilege escalation, enabling the execution of arbitrary code with root user privileges. This weakness stems from the absence of proper sandboxing during the expansion and execution of Jinja2 templates, which may be included in certain configuration parameters. While the Mock documentation advises treating users added to the mock group as privileged, certain build systems invoking mock on behalf of users might inadvertently permit less privileged users to define configuration tags. These tags could then be passed as parameters to mock during execution, potentially leading to the utilization of Jinja2 templates for remote privilege escalation and the execution of arbitrary code as the root user on the build server.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2024-01-16T15:15Z
Repository: https://github.com/xsuchy/templated-dictionary
Language: Python
Files changed: templated_dictionary/__init__.py
Commit date: 2023-12-11 10:09:37 +0100
Commit message: Make the TemplatedDictionary objects picklable

The jinja2 SandboxedEnvironment objects are not guaranteed to be
pickle-serializable (even though they practically are).  So let's rather
store the environment elsewhere than in the TemplatedDictionary object
itself to keep it serializable.

Nb. TemplatedDictionary is just a wrapper around dict, which is quite
commonly subject of (de)serialization (e.g. Mock, the main user of
templated-dictionary library does that through the
concurrent.futures.ProcessPoolExecutor logic).

It would be disadvantageous to introduce additional internal arguments
into the object itself (considering we already have the __jinja_expand
internal).

Having a per-application shared environment is kind of expected anyway
(and even more optimal):

    https://jinja.palletsprojects.com/en/3.0.x/api/#basics

Note that this fix is mostly needed for the RHEL 8 environment with
jinja2 v2.10.1; newer versions made the environments (namely the
included filters) picklable: https://github.com/pallets/jinja/pull/1613

Diff: diff --git a/templated_dictionary/__init__.py b/templated_dictionary/__init__.py
index ec004c0..be217c0 100644
--- a/templated_dictionary/__init__.py
+++ b/templated_dictionary/__init__.py
@@ -2,10 +2,11 @@
 # vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0:
 
 from collections.abc import MutableMapping
 from jinja2 import sandbox
 
+SANDBOX = sandbox.SandboxedEnvironment(keep_trailing_newline=True)
 
 # pylint: disable=no-member,unsupported-assignment-operation
 class TemplatedDictionary(MutableMapping):
     """ Dictionary where __getitem__() is run through Jinja2 template """
     def __init__(self, *args, alias_spec=None, **kwargs):
@@ -17,12 +18,10 @@ class TemplatedDictionary(MutableMapping):
         When specified, and one of the aliases is accessed - the
         'aliased_to' config option is returned.
         '''
         self.__dict__.update(*args, **kwargs)
 
-        self.sandbox = sandbox.SandboxedEnvironment(keep_trailing_newline=True)
-
         self._aliases = {}
         if alias_spec:
             for aliased_to, aliases in alias_spec.items():
                 for alias in aliases:
                     self._aliases[alias] = aliased_to
@@ -80,11 +79,11 @@ class TemplatedDictionary(MutableMapping):
 
     def __render_string(self, value):
         orig = last = value
         max_recursion = self.__dict__.get('jinja_max_recursion', 5)
         for _ in range(max_recursion):
-            value = _to_native(self.sandbox.from_string(value).render(self.__dict__, func=lambda:None))
+            value = _to_native(SANDBOX.from_string(value).render(self.__dict__, func=lambda:None))
             if value == last:
                 return value
             last = value
         raise ValueError("too deep jinja re-evaluation on '{}'".format(orig))
 

Vulnerable code:         self.sandbox = sandbox.SandboxedEnvironment(keep_trailing_newline=True)

            value = _to_native(self.sandbox.from_string(value).render(self.__dict__, func=lambda:None))
Fixed code: SANDBOX = sandbox.SandboxedEnvironment(keep_trailing_newline=True)
            value = _to_native(SANDBOX.from_string(value).render(self.__dict__, func=lambda:None))