attr filter uses env.getattr

This commit is contained in:
David Lord 2025-03-05 10:08:48 -08:00
parent 033c20015c
commit 065334d1ee
No known key found for this signature in database
GPG Key ID: 43368A7AA8CC5926
3 changed files with 29 additions and 20 deletions

View File

@ -5,6 +5,10 @@ Version 3.1.6
Unreleased
- The ``|attr`` filter does not bypass the environment's attribute lookup,
allowing the sandbox to apply its checks. :ghsa:`cpwx-vrp4-4pq7`
Version 3.1.5
-------------

View File

@ -6,6 +6,7 @@ import re
import typing
import typing as t
from collections import abc
from inspect import getattr_static
from itertools import chain
from itertools import groupby
@ -1411,31 +1412,25 @@ def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]
def do_attr(
environment: "Environment", obj: t.Any, name: str
) -> t.Union[Undefined, t.Any]:
"""Get an attribute of an object. ``foo|attr("bar")`` works like
``foo.bar`` just that always an attribute is returned and items are not
looked up.
"""Get an attribute of an object. ``foo|attr("bar")`` works like
``foo.bar``, but returns undefined instead of falling back to ``foo["bar"]``
if the attribute doesn't exist.
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
"""
# Environment.getattr will fall back to obj[name] if obj.name doesn't exist.
# But we want to call env.getattr to get behavior such as sandboxing.
# Determine if the attr exists first, so we know the fallback won't trigger.
try:
name = str(name)
except UnicodeError:
pass
else:
try:
value = getattr(obj, name)
except AttributeError:
pass
else:
if environment.sandboxed:
environment = t.cast("SandboxedEnvironment", environment)
# This avoids executing properties/descriptors, but misses __getattr__
# and __getattribute__ dynamic attrs.
getattr_static(obj, name)
except AttributeError:
# This finds dynamic attrs, and we know it's not a descriptor at this point.
if not hasattr(obj, name):
return environment.undefined(obj=obj, name=name)
if not environment.is_safe_attribute(obj, name, value):
return environment.unsafe_undefined(obj, name)
return value
return environment.undefined(obj=obj, name=name)
return environment.getattr(obj, name)
@typing.overload

View File

@ -190,3 +190,13 @@ class TestStringFormatMap:
with pytest.raises(SecurityError):
t.render()
def test_attr_filter(self) -> None:
env = SandboxedEnvironment()
t = env.from_string(
"""{{ "{0.__call__.__builtins__[__import__]}"
| attr("format")(not_here) }}"""
)
with pytest.raises(SecurityError):
t.render()