summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2022-01-02 00:37:40 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-01 07:56:29 +0100
commitc27a7eb9f40b64990398978152e62b6ff839c2e6 (patch)
treee96c6befb58708ce58e37d6b139b890e60bd9dd5
parent4cafd3aacb0e7bc583f838ef2b0293786b329471 (diff)
[2.2.x] Fixed CVE-2022-22818 -- Fixed possible XSS via {% debug %} template tag.
Thanks Keryn Knight for the report. Backport of 394517f07886495efcf79f95c7ee402a9437bd68 from main. Co-authored-by: Adam Johnson <me@adamj.eu>
-rw-r--r--django/template/defaulttags.py9
-rw-r--r--docs/ref/templates/builtins.txt8
-rw-r--r--docs/releases/2.2.27.txt10
-rw-r--r--tests/template_tests/syntax_tests/test_debug.py46
-rw-r--r--tests/template_tests/tests.py10
5 files changed, 68 insertions, 15 deletions
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index c4a37c25dd..31fa279ca0 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -8,7 +8,7 @@ from itertools import cycle as itertools_cycle, groupby
from django.conf import settings
from django.utils import timezone
-from django.utils.html import conditional_escape, format_html
+from django.utils.html import conditional_escape, escape, format_html
from django.utils.lorem_ipsum import paragraphs, words
from django.utils.safestring import mark_safe
@@ -94,10 +94,13 @@ class CycleNode(Node):
class DebugNode(Node):
def render(self, context):
+ if not settings.DEBUG:
+ return ''
+
from pprint import pformat
- output = [pformat(val) for val in context]
+ output = [escape(pformat(val)) for val in context]
output.append('\n\n')
- output.append(pformat(sys.modules))
+ output.append(escape(pformat(sys.modules)))
return ''.join(output)
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index bc24308ba4..c4b0fa3987 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -194,7 +194,13 @@ from its first value when it's next encountered.
---------
Outputs a whole load of debugging information, including the current context
-and imported modules.
+and imported modules. ``{% debug %}`` outputs nothing when the :setting:`DEBUG`
+setting is ``False``.
+
+.. versionchanged:: 2.2.27
+
+ In older versions, debugging information was displayed when the
+ :setting:`DEBUG` setting was ``False``.
.. templatetag:: extends
diff --git a/docs/releases/2.2.27.txt b/docs/releases/2.2.27.txt
index a35082fa33..b1712c649c 100644
--- a/docs/releases/2.2.27.txt
+++ b/docs/releases/2.2.27.txt
@@ -6,4 +6,12 @@ Django 2.2.27 release notes
Django 2.2.27 fixes two security issues with severity "medium" in 2.2.26.
-...
+CVE-2022-22818: Possible XSS via ``{% debug %}`` template tag
+=============================================================
+
+The ``{% debug %}`` template tag didn't properly encode the current context,
+posing an XSS attack vector.
+
+In order to avoid this vulnerability, ``{% debug %}`` no longer outputs an
+information when the ``DEBUG`` setting is ``False``, and it ensures all context
+variables are correctly escaped when the ``DEBUG`` setting is ``True``.
diff --git a/tests/template_tests/syntax_tests/test_debug.py b/tests/template_tests/syntax_tests/test_debug.py
new file mode 100644
index 0000000000..17fea44b68
--- /dev/null
+++ b/tests/template_tests/syntax_tests/test_debug.py
@@ -0,0 +1,46 @@
+from django.contrib.auth.models import Group
+from django.test import SimpleTestCase, override_settings
+
+from ..utils import setup
+
+
+@override_settings(DEBUG=True)
+class DebugTests(SimpleTestCase):
+
+ @override_settings(DEBUG=False)
+ @setup({'non_debug': '{% debug %}'})
+ def test_non_debug(self):
+ output = self.engine.render_to_string('non_debug', {})
+ self.assertEqual(output, '')
+
+ @setup({'modules': '{% debug %}'})
+ def test_modules(self):
+ output = self.engine.render_to_string('modules', {})
+ self.assertIn(
+ '&#39;django&#39;: &lt;module &#39;django&#39; ',
+ output,
+ )
+
+ @setup({'plain': '{% debug %}'})
+ def test_plain(self):
+ output = self.engine.render_to_string('plain', {'a': 1})
+ self.assertTrue(output.startswith(
+ '{&#39;a&#39;: 1}'
+ '{&#39;False&#39;: False, &#39;None&#39;: None, '
+ '&#39;True&#39;: True}\n\n{'
+ ))
+
+ @setup({'non_ascii': '{% debug %}'})
+ def test_non_ascii(self):
+ group = Group(name="清風")
+ output = self.engine.render_to_string('non_ascii', {'group': group})
+ self.assertTrue(output.startswith(
+ '{&#39;group&#39;: &lt;Group: 清風&gt;}'
+ ))
+
+ @setup({'script': '{% debug %}'})
+ def test_script(self):
+ output = self.engine.render_to_string('script', {'frag': '<script>'})
+ self.assertTrue(output.startswith(
+ '{&#39;frag&#39;: &#39;&lt;script&gt;&#39;}'
+ ))
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index a8f089c351..946daa4570 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -1,6 +1,5 @@
import sys
-from django.contrib.auth.models import Group
from django.template import Context, Engine, TemplateSyntaxError
from django.template.base import UNKNOWN_SOURCE
from django.test import SimpleTestCase, override_settings
@@ -143,15 +142,6 @@ class TemplateTests(SimpleTestCase):
with self.assertRaises(NoReverseMatch):
t.render(Context())
- def test_debug_tag_non_ascii(self):
- """
- #23060 -- Test non-ASCII model representation in debug output.
- """
- group = Group(name="清風")
- c1 = Context({"objs": [group]})
- t1 = Engine().from_string('{% debug %}')
- self.assertIn("清風", t1.render(c1))
-
def test_extends_generic_template(self):
"""
#24338 -- Allow extending django.template.backends.django.Template