| # -*- coding: utf-8 -*- |
| """ |
| jinja2.testsuite.security |
| ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| Checks the sandbox and other security features. |
| |
| :copyright: (c) 2010 by the Jinja Team. |
| :license: BSD, see LICENSE for more details. |
| """ |
| import pytest |
| |
| from jinja2 import Environment |
| from jinja2.sandbox import SandboxedEnvironment, \ |
| ImmutableSandboxedEnvironment, unsafe, has_format |
| from jinja2 import Markup, escape |
| from jinja2.exceptions import SecurityError, TemplateSyntaxError, \ |
| TemplateRuntimeError |
| from jinja2._compat import text_type |
| |
| |
| class PrivateStuff(object): |
| |
| def bar(self): |
| return 23 |
| |
| @unsafe |
| def foo(self): |
| return 42 |
| |
| def __repr__(self): |
| return 'PrivateStuff' |
| |
| |
| class PublicStuff(object): |
| bar = lambda self: 23 |
| _foo = lambda self: 42 |
| |
| def __repr__(self): |
| return 'PublicStuff' |
| |
| |
| @pytest.mark.sandbox |
| class TestSandbox(): |
| |
| def test_unsafe(self, env): |
| env = SandboxedEnvironment() |
| pytest.raises(SecurityError, env.from_string("{{ foo.foo() }}").render, |
| foo=PrivateStuff()) |
| assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == '23' |
| |
| pytest.raises(SecurityError, |
| env.from_string("{{ foo._foo() }}").render, |
| foo=PublicStuff()) |
| assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == '23' |
| assert env.from_string("{{ foo.__class__ }}").render(foo=42) == '' |
| assert env.from_string("{{ foo.func_code }}").render(foo=lambda:None) == '' |
| # security error comes from __class__ already. |
| pytest.raises(SecurityError, env.from_string( |
| "{{ foo.__class__.__subclasses__() }}").render, foo=42) |
| |
| def test_immutable_environment(self, env): |
| env = ImmutableSandboxedEnvironment() |
| pytest.raises(SecurityError, env.from_string( |
| '{{ [].append(23) }}').render) |
| pytest.raises(SecurityError, env.from_string( |
| '{{ {1:2}.clear() }}').render) |
| |
| def test_restricted(self, env): |
| env = SandboxedEnvironment() |
| pytest.raises(TemplateSyntaxError, env.from_string, |
| "{% for item.attribute in seq %}...{% endfor %}") |
| pytest.raises(TemplateSyntaxError, env.from_string, |
| "{% for foo, bar.baz in seq %}...{% endfor %}") |
| |
| def test_markup_operations(self, env): |
| # adding two strings should escape the unsafe one |
| unsafe = '<script type="application/x-some-script">alert("foo");</script>' |
| safe = Markup('<em>username</em>') |
| assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe) |
| |
| # string interpolations are safe to use too |
| assert Markup('<em>%s</em>') % '<bad user>' == \ |
| '<em><bad user></em>' |
| assert Markup('<em>%(username)s</em>') % { |
| 'username': '<bad user>' |
| } == '<em><bad user></em>' |
| |
| # an escaped object is markup too |
| assert type(Markup('foo') + 'bar') is Markup |
| |
| # and it implements __html__ by returning itself |
| x = Markup("foo") |
| assert x.__html__() is x |
| |
| # it also knows how to treat __html__ objects |
| class Foo(object): |
| def __html__(self): |
| return '<em>awesome</em>' |
| |
| def __unicode__(self): |
| return 'awesome' |
| assert Markup(Foo()) == '<em>awesome</em>' |
| assert Markup('<strong>%s</strong>') % Foo() == \ |
| '<strong><em>awesome</em></strong>' |
| |
| # escaping and unescaping |
| assert escape('"<>&\'') == '"<>&'' |
| assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar" |
| assert Markup("<test>").unescape() == "<test>" |
| |
| def test_template_data(self, env): |
| env = Environment(autoescape=True) |
| t = env.from_string('{% macro say_hello(name) %}' |
| '<p>Hello {{ name }}!</p>{% endmacro %}' |
| '{{ say_hello("<blink>foo</blink>") }}') |
| escaped_out = '<p>Hello <blink>foo</blink>!</p>' |
| assert t.render() == escaped_out |
| assert text_type(t.module) == escaped_out |
| assert escape(t.module) == escaped_out |
| assert t.module.say_hello('<blink>foo</blink>') == escaped_out |
| assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out |
| |
| def test_attr_filter(self, env): |
| env = SandboxedEnvironment() |
| tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}') |
| pytest.raises(SecurityError, tmpl.render, cls=int) |
| |
| def test_binary_operator_intercepting(self, env): |
| def disable_op(left, right): |
| raise TemplateRuntimeError('that operator so does not work') |
| for expr, ctx, rv in ('1 + 2', {}, '3'), ('a + 2', {'a': 2}, '4'): |
| env = SandboxedEnvironment() |
| env.binop_table['+'] = disable_op |
| t = env.from_string('{{ %s }}' % expr) |
| assert t.render(ctx) == rv |
| env.intercepted_binops = frozenset(['+']) |
| t = env.from_string('{{ %s }}' % expr) |
| try: |
| t.render(ctx) |
| except TemplateRuntimeError as e: |
| pass |
| else: |
| assert False, 'expected runtime error' |
| |
| def test_unary_operator_intercepting(self, env): |
| def disable_op(arg): |
| raise TemplateRuntimeError('that operator so does not work') |
| for expr, ctx, rv in ('-1', {}, '-1'), ('-a', {'a': 2}, '-2'): |
| env = SandboxedEnvironment() |
| env.unop_table['-'] = disable_op |
| t = env.from_string('{{ %s }}' % expr) |
| assert t.render(ctx) == rv |
| env.intercepted_unops = frozenset(['-']) |
| t = env.from_string('{{ %s }}' % expr) |
| try: |
| t.render(ctx) |
| except TemplateRuntimeError as e: |
| pass |
| else: |
| assert False, 'expected runtime error' |
| |
| |
| @pytest.mark.sandbox |
| @pytest.mark.skipif(not has_format, reason='No format support') |
| class TestStringFormat(object): |
| |
| def test_basic_format_safety(self): |
| env = SandboxedEnvironment() |
| t = env.from_string('{{ "a{0.__class__}b".format(42) }}') |
| assert t.render() == 'ab' |
| |
| def test_basic_format_all_okay(self): |
| env = SandboxedEnvironment() |
| t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}') |
| assert t.render() == 'a42b' |
| |
| def test_safe_format_safety(self): |
| env = SandboxedEnvironment() |
| t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}') |
| assert t.render() == 'ab<foo>' |
| |
| def test_safe_format_all_okay(self): |
| env = SandboxedEnvironment() |
| t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}') |
| assert t.render() == 'a42b<foo>' |