Skip to content

Commit

Permalink
Merge pull request #1616 from pallets/docs-sandbox
Browse files Browse the repository at this point in the history
rewrite sandbox docs, add more security discussion
  • Loading branch information
davidism authored Mar 12, 2022
2 parents 186aa82 + 165e972 commit 350bc53
Showing 1 changed file with 75 additions and 58 deletions.
133 changes: 75 additions & 58 deletions docs/sandbox.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
Sandbox
=======

The Jinja sandbox can be used to evaluate untrusted code. Access to unsafe
attributes and methods is prohibited.
The Jinja sandbox can be used to render untrusted templates. Access to
attributes, method calls, operators, mutating data structures, and
string formatting can be intercepted and prohibited.

Assuming `env` is a :class:`SandboxedEnvironment` in the default configuration
the following piece of code shows how it works:
.. code-block:: pycon
>>> 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.
A sandboxed environment can be useful, for example, to allow users of an
internal reporting system to create custom emails. You would document
what data is available in the templates, then the user would write a
template using that information. Your code would generate the report
data and pass it to the user's sandboxed template to render.


Security Considerations
-----------------------

The sandbox alone is not a solution for perfect security. Keep these
things in mind when using the sandbox.

Templates can still raise errors when compiled or rendered. Your code
should attempt to catch errors instead of crashing.

It is possible to construct a relatively small template that renders to
a very large amount of output, which could correspond to a high use of
CPU or memory. You should run your application with limits on resources
such as CPU and memory to mitigate this.

Jinja only renders text, it does not understand, for example, JavaScript
code. Depending on how the rendered template will be used, you may need
to do other postprocessing to restrict the output.

Pass only the data that is relevant to the template. Avoid passing
global data, or objects with methods that have side effects. By default
the sandbox prevents private and internal attribute access. You can
override :meth:`~SandboxedEnvironment.is_safe_attribute` to further
restrict attributes access. Decorate methods with :func:`unsafe` to
prevent calling them from templates when passing objects as data. Use
:class:`ImmutableSandboxedEnvironment` to prevent modifying lists and
dictionaries.

>>> env.from_string("{{ func.func_code }}").render(func=lambda:None)
u''
>>> env.from_string("{{ func.func_code.do_something }}").render(func=lambda:None)
Traceback (most recent call last):
...
SecurityError: access to attribute 'func_code' of 'function' object is unsafe.

API
---
Expand All @@ -34,61 +72,40 @@ API

.. autofunction:: modifies_known_mutable

.. admonition:: Note

The Jinja sandbox alone is no solution for perfect security. Especially
for web applications you have to keep in mind that users may create
templates with arbitrary HTML in so it's crucial to ensure that (if you
are running multiple users on the same server) they can't harm each other
via JavaScript insertions and much more.

Also the sandbox is only as good as the configuration. We strongly
recommend only passing non-shared resources to the template and use
some sort of whitelisting for attributes.

Also keep in mind that templates may raise runtime or compile time errors,
so make sure to catch them.

Operator Intercepting
---------------------

.. versionadded:: 2.6

For maximum performance Jinja will let operators call directly the type
specific callback methods. This means that it's not possible to have this
intercepted by overriding :meth:`Environment.call`. Furthermore a
conversion from operator to special method is not always directly possible
due to how operators work. For instance for divisions more than one
special method exist.

With Jinja 2.6 there is now support for explicit operator intercepting.
This can be used to customize specific operators as necessary. In order
to intercept an operator one has to override the
:attr:`SandboxedEnvironment.intercepted_binops` attribute. Once the
operator that needs to be intercepted is added to that set Jinja will
generate bytecode that calls the :meth:`SandboxedEnvironment.call_binop`
function. For unary operators the `unary` attributes and methods have to
be used instead.

The default implementation of :attr:`SandboxedEnvironment.call_binop`
will use the :attr:`SandboxedEnvironment.binop_table` to translate
operator symbols into callbacks performing the default operator behavior.

This example shows how the power (``**``) operator can be disabled in
Jinja::
For performance, Jinja outputs operators directly when compiling. This
means it's not possible to intercept operator behavior by overriding
:meth:`SandboxEnvironment.call <Environment.call>` by default, because
operator special methods are handled by the Python interpreter, and
might not correspond with exactly one method depending on the operator's
use.

The sandbox can instruct the compiler to output a function to intercept
certain operators instead. Override
:attr:`SandboxedEnvironment.intercepted_binops` and
:attr:`SandboxedEnvironment.intercepted_unops` with the operator symbols
you want to intercept. The compiler will replace the symbols with calls
to :meth:`SandboxedEnvironment.call_binop` and
:meth:`SandboxedEnvironment.call_unop` instead. The default
implementation of those methods will use
:attr:`SandboxedEnvironment.binop_table` and
:attr:`SandboxedEnvironment.unop_table` to translate operator symbols
into :mod:`operator` functions.

For example, the power (``**``) operator can be disabled:

.. code-block:: python
from jinja2.sandbox import SandboxedEnvironment

class MyEnvironment(SandboxedEnvironment):
intercepted_binops = frozenset(['**'])
intercepted_binops = frozenset(["**"])
def call_binop(self, context, operator, left, right):
if operator == '**':
return self.undefined('the power operator is unavailable')
return SandboxedEnvironment.call_binop(self, context,
operator, left, right)

Make sure to always call into the super method, even if you are not
intercepting the call. Jinja might internally call the method to
evaluate expressions.
if operator == "**":
return self.undefined("The power (**) operator is unavailable.")
return super().call_binop(self, context, operator, left, right)

0 comments on commit 350bc53

Please # to comment.