File CVE-2021-3281.patch of Package python-Django1
From 77aafbae422e69fd96fffde95ad4af71bd39ce27 Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Fri, 22 Jan 2021 12:23:18 +0100
Subject: [PATCH] [2.2.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 bb76e00d125821fcf887c6e0ca3fd081f61d6e5e from master.
---
django/utils/archive.py | 17 +++++++++++---
tests/utils_tests/test_archive.py | 21 ++++++++++++++++++
.../traversal_archives/traversal.tar | Bin 0 -> 10240 bytes
.../traversal_archives/traversal_absolute.tar | Bin 0 -> 10240 bytes
.../traversal_archives/traversal_disk_win.tar | Bin 0 -> 10240 bytes
.../traversal_archives/traversal_disk_win.zip | Bin 0 -> 312 bytes
8 files changed, 51 insertions(+), 3 deletions(-)
create mode 100644 tests/utils_tests/traversal_archives/traversal.tar
create mode 100644 tests/utils_tests/traversal_archives/traversal_absolute.tar
create mode 100644 tests/utils_tests/traversal_archives/traversal_disk_win.tar
create mode 100644 tests/utils_tests/traversal_archives/traversal_disk_win.zip
Index: Django-1.11.29/django/utils/archive.py
===================================================================
--- Django-1.11.29.orig/django/utils/archive.py
+++ Django-1.11.29/django/utils/archive.py
@@ -27,6 +27,7 @@ import stat
import tarfile
import zipfile
+from django.core.exceptions import SuspiciousOperation
from django.utils import six
@@ -135,6 +136,13 @@ class BaseArchive(object):
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')
@@ -157,7 +165,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 and not os.path.exists(filename):
os.makedirs(filename)
@@ -200,11 +208,13 @@ 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 not name:
+ continue
+ filename = self.target_filename(to_path, name)
dirname = os.path.dirname(filename)
if dirname and not os.path.exists(dirname):
os.makedirs(dirname)
- if filename.endswith(('/', '\\')):
+ if name.endswith(('/', '\\')):
# A directory
if not os.path.exists(filename):
os.makedirs(filename)
Index: Django-1.11.29/tests/utils_tests/test_archive.py
===================================================================
--- Django-1.11.29.orig/tests/utils_tests/test_archive.py
+++ Django-1.11.29/tests/utils_tests/test_archive.py
@@ -5,6 +5,8 @@ import sys
import tempfile
import unittest
+from django.core.exceptions import SuspiciousOperation
+from django.test import SimpleTestCase
from django.utils._os import upath
from django.utils.archive import Archive, extract
@@ -88,3 +90,22 @@ class TestGzipTar(ArchiveTester, unittes
class TestBzip2Tar(ArchiveTester, unittest.TestCase):
archive = 'foobar.tar.bz2'
+
+
+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):
+ extract(os.path.join(archives_dir, entry), tmpdir)