Fixed #33986 -- Hardened binary lookup in template commands.

Made template commands look up formatters before writing files.
This makes sure files included in the template are not identified
as executable formatter commands, even in case the template is
rendered into the system path (as might easily happen on Windows,
where the current directory is on the system path by default).

While at it, Warned about trusting custom templates for
startapp/startproject.

Thanks Trung Pham of Viettel Cyber Security for reporting the issue,
Django Security Team for discussions, and Adam Johnson and
Carlton Gibson for reviews.
This commit is contained in:
Shai Berger 2022-08-24 12:38:22 +03:00 committed by Carlton Gibson
parent 19e838daa8
commit 42cd8c390d
3 changed files with 35 additions and 6 deletions

View File

@ -11,7 +11,11 @@ from urllib.request import build_opener
import django
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.core.management.utils import handle_extensions, run_formatters
from django.core.management.utils import (
find_formatters,
handle_extensions,
run_formatters,
)
from django.template import Context, Engine
from django.utils import archive
from django.utils.http import parse_header_parameters
@ -107,6 +111,10 @@ class TemplateCommand(BaseCommand):
"exist, please create it first." % top_dir
)
# Find formatters, which are external executables, before input
# from the templates can sneak into the path.
formatter_paths = find_formatters()
extensions = tuple(handle_extensions(options["extensions"]))
extra_files = []
excluded_directories = [".git", "__pycache__"]
@ -224,7 +232,7 @@ class TemplateCommand(BaseCommand):
else:
shutil.rmtree(path_to_remove)
run_formatters(self.written_files)
run_formatters(self.written_files, **formatter_paths)
def handle_template(self, template, subdir):
"""

View File

@ -157,11 +157,18 @@ def is_ignored_path(path, ignore_patterns):
return any(ignore(pattern) for pattern in normalize_path_patterns(ignore_patterns))
def run_formatters(written_files):
def find_formatters():
return {"black_path": shutil.which("black")}
def run_formatters(written_files, black_path=(sentinel := object())):
"""
Run the black formatter on the specified files.
"""
if black_path := shutil.which("black"):
# Use a sentinel rather than None, as which() returns None when not found.
if black_path is sentinel:
black_path = shutil.which("black")
if black_path:
subprocess.run(
[black_path, "--fast", "--", *written_files],
capture_output=True,

View File

@ -1362,6 +1362,19 @@ files is:
byte-compile invalid ``*.py`` files, template files ending with ``.py-tpl``
will be renamed to ``.py``.
.. _trusted_code_warning:
.. warning::
The contents of custom app (or project) templates should always be
audited before use: Such templates define code that will become
part of your project, and this means that such code will be trusted
as much as any app you install, or code you write yourself.
Further, even rendering the templates is, effectively, executing
code that was provided as input to the management command. The
Django template language may provide wide access into the system,
so make sure any custom template you use is worthy of your trust.
``startproject``
----------------
@ -1418,8 +1431,9 @@ The :class:`template context <django.template.Context>` used is:
- ``docs_version`` -- the version of the documentation: ``'dev'`` or ``'1.x'``
- ``django_version`` -- the version of Django, e.g. ``'2.0.3'``
Please also see the :ref:`rendering warning <render_warning>` as mentioned
for :djadmin:`startapp`.
Please also see the :ref:`rendering warning <render_warning>` and
:ref:`trusted code warning <trusted_code_warning>` as mentioned for
:djadmin:`startapp`.
``test``
--------