summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2021-01-22 12:23:18 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-02-01 09:14:22 +0100
commit52e409ed17287e9aabda847b6afe58be2fa9f86a (patch)
treea18a930cde1bf98db732b69635558a6a4db650b7
parent74ca3cac3272b3a9df6a351095fe4117d0cf2608 (diff)
downloaddjango-52e409ed17287e9aabda847b6afe58be2fa9f86a.tar.gz
[3.0.x] Fixed CVE-2021-3281 -- Fixed potential directory-traversal via archive.extract().
Thanks Florian Apolloner, Shai Berger, and Simon Charette for reviews. Thanks Wang Baohua for the report. Backport of 05413afa8c18cdb978fcdf470e09f7a12b234a23 from master.
-rw-r--r--django/utils/archive.py17
-rw-r--r--docs/releases/2.2.18.txt15
-rw-r--r--docs/releases/3.0.12.txt15
-rw-r--r--docs/releases/index.txt2
-rw-r--r--tests/utils_tests/test_archive.py21
-rw-r--r--tests/utils_tests/traversal_archives/traversal.tarbin0 -> 10240 bytes
-rw-r--r--tests/utils_tests/traversal_archives/traversal_absolute.tarbin0 -> 10240 bytes
-rw-r--r--tests/utils_tests/traversal_archives/traversal_disk_win.tarbin0 -> 10240 bytes
-rw-r--r--tests/utils_tests/traversal_archives/traversal_disk_win.zipbin0 -> 312 bytes
9 files changed, 67 insertions, 3 deletions
diff --git a/django/utils/archive.py b/django/utils/archive.py
index 235809f2ad..d5a0cf0446 100644
--- a/django/utils/archive.py
+++ b/django/utils/archive.py
@@ -27,6 +27,8 @@ import stat
import tarfile
import zipfile
+from django.core.exceptions import SuspiciousOperation
+
class ArchiveException(Exception):
"""
@@ -133,6 +135,13 @@ class BaseArchive:
return False
return True
+ def target_filename(self, to_path, name):
+ target_path = os.path.abspath(to_path)
+ filename = os.path.abspath(os.path.join(target_path, name))
+ if not filename.startswith(target_path):
+ raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
+ return filename
+
def extract(self):
raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
@@ -155,7 +164,7 @@ class TarArchive(BaseArchive):
name = member.name
if leading:
name = self.split_leading_dir(name)[1]
- filename = os.path.join(to_path, name)
+ filename = self.target_filename(to_path, name)
if member.isdir():
if filename:
os.makedirs(filename, exist_ok=True)
@@ -198,8 +207,10 @@ class ZipArchive(BaseArchive):
info = self._archive.getinfo(name)
if leading:
name = self.split_leading_dir(name)[1]
- filename = os.path.join(to_path, name)
- if filename.endswith(('/', '\\')):
+ if not name:
+ continue
+ filename = self.target_filename(to_path, name)
+ if name.endswith(('/', '\\')):
# A directory
os.makedirs(filename, exist_ok=True)
else:
diff --git a/docs/releases/2.2.18.txt b/docs/releases/2.2.18.txt
new file mode 100644
index 0000000000..45df4fb83c
--- /dev/null
+++ b/docs/releases/2.2.18.txt
@@ -0,0 +1,15 @@
+===========================
+Django 2.2.18 release notes
+===========================
+
+*February 1, 2021*
+
+Django 2.2.18 fixes a security issue with severity "low" in 2.2.17.
+
+CVE-2021-3281: Potential directory-traversal via ``archive.extract()``
+======================================================================
+
+The ``django.utils.archive.extract()`` function, used by
+:option:`startapp --template` and :option:`startproject --template`, allowed
+directory-traversal via an archive with absolute paths or relative paths with
+dot segments.
diff --git a/docs/releases/3.0.12.txt b/docs/releases/3.0.12.txt
new file mode 100644
index 0000000000..20d9459fea
--- /dev/null
+++ b/docs/releases/3.0.12.txt
@@ -0,0 +1,15 @@
+===========================
+Django 3.0.12 release notes
+===========================
+
+*February 1, 2021*
+
+Django 3.0.12 fixes a security issue with severity "low" in 3.0.11.
+
+CVE-2021-3281: Potential directory-traversal via ``archive.extract()``
+======================================================================
+
+The ``django.utils.archive.extract()`` function, used by
+:option:`startapp --template` and :option:`startproject --template`, allowed
+directory-traversal via an archive with absolute paths or relative paths with
+dot segments.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index b08af9fa51..49281fa9b5 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 3.0.12
3.0.11
3.0.10
3.0.9
@@ -43,6 +44,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 2.2.18
2.2.17
2.2.16
2.2.15
diff --git a/tests/utils_tests/test_archive.py b/tests/utils_tests/test_archive.py
index dc7c4b4ebd..8fdf3ec445 100644
--- a/tests/utils_tests/test_archive.py
+++ b/tests/utils_tests/test_archive.py
@@ -4,6 +4,8 @@ import sys
import tempfile
import unittest
+from django.core.exceptions import SuspiciousOperation
+from django.test import SimpleTestCase
from django.utils import archive
@@ -45,3 +47,22 @@ class TestArchive(unittest.TestCase):
# A file is readable even if permission data is missing.
filepath = os.path.join(tmpdir, 'no_permissions')
self.assertEqual(os.stat(filepath).st_mode & mask, 0o666 & ~umask)
+
+
+class TestArchiveInvalid(SimpleTestCase):
+ def test_extract_function_traversal(self):
+ archives_dir = os.path.join(os.path.dirname(__file__), 'traversal_archives')
+ tests = [
+ ('traversal.tar', '..'),
+ ('traversal_absolute.tar', '/tmp/evil.py'),
+ ]
+ if sys.platform == 'win32':
+ tests += [
+ ('traversal_disk_win.tar', 'd:evil.py'),
+ ('traversal_disk_win.zip', 'd:evil.py'),
+ ]
+ msg = "Archive contains invalid path: '%s'"
+ for entry, invalid_path in tests:
+ with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
+ with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
+ archive.extract(os.path.join(archives_dir, entry), tmpdir)
diff --git a/tests/utils_tests/traversal_archives/traversal.tar b/tests/utils_tests/traversal_archives/traversal.tar
new file mode 100644
index 0000000000..07eede517a
--- /dev/null
+++ b/tests/utils_tests/traversal_archives/traversal.tar
Binary files differ
diff --git a/tests/utils_tests/traversal_archives/traversal_absolute.tar b/tests/utils_tests/traversal_archives/traversal_absolute.tar
new file mode 100644
index 0000000000..231566b069
--- /dev/null
+++ b/tests/utils_tests/traversal_archives/traversal_absolute.tar
Binary files differ
diff --git a/tests/utils_tests/traversal_archives/traversal_disk_win.tar b/tests/utils_tests/traversal_archives/traversal_disk_win.tar
new file mode 100644
index 0000000000..97f0b95501
--- /dev/null
+++ b/tests/utils_tests/traversal_archives/traversal_disk_win.tar
Binary files differ
diff --git a/tests/utils_tests/traversal_archives/traversal_disk_win.zip b/tests/utils_tests/traversal_archives/traversal_disk_win.zip
new file mode 100644
index 0000000000..e5ab208398
--- /dev/null
+++ b/tests/utils_tests/traversal_archives/traversal_disk_win.zip
Binary files differ