沙盒¶
Jinja 沙盒可用于渲染不受信任的模板。对属性、方法调用、运算符、修改数据结构和字符串格式的访问可以被拦截和禁止。
>>> from jinja2.sandbox import SandboxedEnvironment
>>> env = SandboxedEnvironment()
>>> func = lambda: "Hello, Sandbox!"
>>> env.from_string("{{ func() }}").render(func=func)
'Hello, Sandbox!'
>>> env.from_string("{{ func.__code__.co_code }}").render(func=func)
Traceback (most recent call last):
...
SecurityError: access to attribute '__code__' of 'function' object is unsafe.
沙盒环境很有用,例如,允许内部报告系统的用户创建自定义电子邮件。你可以记录模板中可用的数据,然后用户将使用该信息编写模板。你的代码将生成报告数据,并将其传递给用户的沙盒模板进行渲染。
安全注意事项¶
沙盒本身并不是完美安全的解决方案。在使用沙盒时,请记住以下事项。
模板在编译或渲染时仍可能引发错误。你的代码应尝试捕获错误,而不是崩溃。
有可能构建一个相对较小的模板,将其渲染为非常大量的输出,这可能对应于高 CPU 或内存使用率。你应该对你的应用程序运行资源限制(例如 CPU 和内存),以缓解这种情况。
Jinja 仅渲染文本,它不理解,例如,JavaScript 代码。根据渲染模板的使用方式,你可能需要执行其他后处理来限制输出。
仅传递与模板相关的数据。避免传递全局数据或具有副作用的方法的对象。默认情况下,沙盒会阻止私有和内部属性访问。你可以覆盖 is_safe_attribute()
以进一步限制属性访问。使用 unsafe()
装饰方法,以在将对象作为数据传递时阻止从模板中调用它们。使用 ImmutableSandboxedEnvironment
来防止修改列表和字典。
API¶
- 类 jinja2.sandbox.SandboxedEnvironment([options])¶
沙盒环境。它的工作方式与常规环境类似,但会告知编译器生成沙盒代码。此外,此环境的子类可以覆盖告诉运行时哪些属性或函数可以安全访问的方法。
如果模板尝试访问不安全的代码,则会引发
SecurityError
。但是,在渲染过程中也可能发生其他异常,因此调用方必须确保捕获所有异常。- default_binop_table: Dict[str, Callable[[Any, Any], Any]] = {'%': <built-in function mod>, '*': <built-in function mul>, '**': <built-in function pow>, '+': <built-in function add>, '-': <built-in function sub>, '/': <built-in function truediv>, '//': <built-in function floordiv>}¶
二进制运算符的默认回调表。沙盒环境的每个实例都可通过
binop_table
访问此表的副本
- default_unop_table: Dict[str, Callable[[Any], Any]] = {'+': <built-in function pos>, '-': <built-in function neg>}¶
一元运算符的默认回调表。沙盒环境的每个实例上都有此副本,作为
unop_table
- intercepted_binops: FrozenSet[str] = frozenset({})¶
应拦截的一组二元运算符。添加到此集合(默认情况下为空)的每个运算符都会委托给
call_binop()
方法,该方法将执行运算符。默认运算符回调由binop_table
指定。可以拦截以下二元运算符:
//
、%
、+
、*
、-
、/
和**
运算符表中的默认运算对应于内置函数。拦截的调用总是比本机运算符调用慢,因此请确保只拦截您感兴趣的调用。
变更日志
在 2.6 版中添加。
- intercepted_unops: FrozenSet[str] = frozenset({})¶
应拦截的一组一元运算符。添加到此集合(默认情况下为空)的每个运算符都会委托给
call_unop()
方法,该方法将执行运算符。默认运算符回调由unop_table
指定。以下一元运算符可拦截:
+
、-
运算符表中的默认运算对应于内置函数。拦截的调用总是比本机运算符调用慢,因此请确保只拦截您感兴趣的调用。
变更日志
在 2.6 版中添加。
- is_safe_attribute(obj, attr, value)¶
沙盒环境将调用此方法以检查对象的属性是否可以安全访问。默认情况下,所有以下划线开头的属性以及
is_internal_attribute()
函数返回的内部 Python 对象的特殊属性都被视为私有属性。
- is_safe_callable(obj)¶
检查对象是否可以安全调用。默认情况下,可调用对象被视为安全,除非用
unsafe()
装饰。这也识别了 Django 设置
func.alters_data = True
的约定。
- call_binop(context, operator, left, right)¶
对于拦截的二元运算符调用(
intercepted_binops()
),此函数将执行,而不是内置运算符。这可用于微调某些运算符的行为。变更日志
在 2.6 版中添加。
- class jinja2.sandbox.ImmutableSandboxedEnvironment([options])¶
与常规的
SandboxedEnvironment
完全相同,但通过使用modifies_known_mutable()
函数,不允许对内置可变对象list
、set
和dict
进行修改。
- exception jinja2.sandbox.SecurityError(message=None)¶
如果在启用沙盒的情况下,模板尝试执行不安全操作,则会引发此异常。
- 参数:
message (str | None)
- 返回类型:
无
- jinja2.sandbox.unsafe(f)¶
将函数或方法标记为不安全。
- 参数:
f (F)
- 返回类型:
F
- jinja2.sandbox.is_internal_attribute(obj, attr)¶
测试给定的属性是否是内部 python 属性。例如,此函数对 python 对象的
func_code
属性返回True
。如果覆盖了环境方法is_safe_attribute()
,则此方法很有用。>>> from jinja2.sandbox import is_internal_attribute >>> is_internal_attribute(str, "mro") True >>> is_internal_attribute(str, "upper") False
- jinja2.sandbox.modifies_known_mutable(obj, attr)¶
此函数检查内置可变对象(列表、字典、集合或双端队列)上的属性或相应的 ABC 是否会在调用时对其进行修改。
>>> modifies_known_mutable({}, "clear") True >>> modifies_known_mutable({}, "keys") False >>> modifies_known_mutable([], "append") True >>> modifies_known_mutable([], "index") False
如果使用不支持的对象调用,则返回
False
。>>> modifies_known_mutable("foo", "upper") False
操作符拦截¶
为了提高性能,Jinja 在编译时直接输出操作符。这意味着默认情况下无法通过覆盖 SandboxEnvironment.call
来拦截操作符行为,因为操作符特殊方法由 Python 解释器处理,并且可能不会与一个方法完全对应,具体取决于操作符的使用情况。
沙箱可以指示编译器输出一个函数来拦截某些操作符。使用要拦截的操作符符号覆盖 SandboxedEnvironment.intercepted_binops
和 SandboxedEnvironment.intercepted_unops
。编译器将用对 SandboxedEnvironment.call_binop()
和 SandboxedEnvironment.call_unop()
的调用来替换符号。这些方法的默认实现将使用 SandboxedEnvironment.binop_table
和 SandboxedEnvironment.unop_table
将操作符符号转换为 operator 函数。
例如,可以禁用幂 (**
) 运算符
from jinja2.sandbox import SandboxedEnvironment
class MyEnvironment(SandboxedEnvironment):
intercepted_binops = frozenset(["**"])
def call_binop(self, context, operator, left, right):
if operator == "**":
return self.undefined("The power (**) operator is unavailable.")
return super().call_binop(self, context, operator, left, right)