1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
Origin: upstream
Last-Update: 2013-09-05
Subject: directory traversal with ``ssi`` template tag
Django's template language includes two methods of including and
rendering one template inside another:
1. The ``{% include %}`` tag takes a template name, and uses Django's
template loading mechanism (which is restricted to the directories
specified in the ``TEMPLATE_DIRS`` setting, as with any other
normal template load in Django).
2. The ``{% ssi %}`` tag, which takes a file path and includes that
file's contents (optionally parsing and rendering it as a
template).
Since the ``ssi`` tag is not restricted to ``TEMPLATE_DIRS``, it
represents a security risk; the setting ``ALLOWED_INCLUDE_ROOTS`` thus
is required, and specifies filesystem locations from which ``ssi`` may
read files.
To remedy this, the ``ssi`` tag will now use Python's
``os.path.abspath`` to determine the absolute path of the file, and
whether it is actually located within a directory permitted by
``ALLOWED_INCLUDE_ROOTS``.
This patch was modified by Debian to port it to the 1.2 Django release,
which is unsupported by upstream.
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -1,5 +1,6 @@
"""Default tags used by the template system, available to all templates."""
+import os
import sys
import re
from itertools import groupby, cycle as itertools_cycle
@@ -280,6 +281,7 @@
return ''
def include_is_allowed(filepath):
+ filepath = os.path.abspath(filepath)
for root in settings.ALLOWED_INCLUDE_ROOTS:
if filepath.startswith(root):
return True
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -1389,5 +1389,37 @@
settings.INSTALLED_APPS = ('tagsegg',)
t = template.Template(ttext)
+class SSITests(unittest.TestCase):
+ def setUp(self):
+ self.this_dir = os.path.dirname(os.path.abspath(__file__))
+ self.ssi_dir = os.path.join(self.this_dir, "templates", "first")
+
+ def render_ssi(self, path):
+ from django.template import Context
+ # the path must exist for the test to be reliable
+ self.assertTrue(os.path.exists(path))
+ return template.Template('{%% ssi %s %%}' % path).render(Context())
+
+ def test_allowed_paths(self):
+ acceptable_path = os.path.join(self.ssi_dir, "..", "first", "test.html")
+ settings.ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)
+ self.assertEqual(self.render_ssi(acceptable_path), 'First template\n')
+
+ def test_relative_include_exploit(self):
+ """
+ May not bypass ALLOWED_INCLUDE_ROOTS with relative paths
+
+ e.g. if ALLOWED_INCLUDE_ROOTS = ("/var/www",), it should not be
+ possible to do {% ssi "/var/www/../../etc/passwd" %}
+ """
+ disallowed_paths = [
+ os.path.join(self.ssi_dir, "..", "ssi_include.html"),
+ os.path.join(self.ssi_dir, "..", "second", "test.html"),
+ ]
+ settings.ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)
+ for path in disallowed_paths:
+ self.assertEqual(self.render_ssi(path), '')
+
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+++ b/tests/regressiontests/templates/templates/ssi_include.html
@@ -0,0 +1 @@
+This is for testing an ssi include. {{ test }}
|