티스토리 수익 글 보기

티스토리 수익 글 보기

[2.2.x] Fixed CVE-2021-3281 — Fixed potential directory-traversal vi… · django/django@21e7622 · GitHub
Skip to content
/ django Public

Commit 21e7622

Browse files
committed
[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 05413af from master.
1 parent ee9d623 commit 21e7622

File tree

8 files changed

+51
3
lines changed

8 files changed

+51
3
lines changed

django/utils/archive.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import tarfile
2828
import zipfile
2929

30+
from django.core.exceptions import SuspiciousOperation
31+
3032

3133
class ArchiveException(Exception):
3234
"""
@@ -133,6 +135,13 @@ def has_leading_dir(self, paths):
133135
return False
134136
return True
135137

138+
def target_filename(self, to_path, name):
139+
target_path = os.path.abspath(to_path)
140+
filename = os.path.abspath(os.path.join(target_path, name))
141+
if not filename.startswith(target_path):
142+
raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
143+
return filename
144+
136145
def extract(self):
137146
raise NotImplementedError('subclasses of BaseArchive must provide an extract() method')
138147

@@ -155,7 +164,7 @@ def extract(self, to_path):
155164
name = member.name
156165
if leading:
157166
name = self.split_leading_dir(name)[1]
158-
filename = os.path.join(to_path, name)
167+
filename = self.target_filename(to_path, name)
159168
if member.isdir():
160169
if filename and not os.path.exists(filename):
161170
os.makedirs(filename)
@@ -198,11 +207,13 @@ def extract(self, to_path):
198207
info = self._archive.getinfo(name)
199208
if leading:
200209
name = self.split_leading_dir(name)[1]
201-
filename = os.path.join(to_path, name)
210+
if not name:
211+
continue
212+
filename = self.target_filename(to_path, name)
202213
dirname = os.path.dirname(filename)
203214
if dirname and not os.path.exists(dirname):
204215
os.makedirs(dirname)
205-
if filename.endswith(('/', '\\')):
216+
if name.endswith(('/', '\\')):
206217
# A directory
207218
if not os.path.exists(filename):
208219
os.makedirs(filename)

docs/releases/2.2.18.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
===========================
2+
Django 2.2.18 release notes
3+
===========================
4+
5+
*February 1, 2021*
6+
7+
Django 2.2.18 fixes a security issue with severity "low" in 2.2.17.
8+
9+
CVE-2021-3281: Potential directory-traversal via ``archive.extract()``
10+
======================================================================
11+
12+
The ``django.utils.archive.extract()`` function, used by
13+
:option:`startapp --template` and :option:`startproject --template`, allowed
14+
directory-traversal via an archive with absolute paths or relative paths with
15+
dot segments.

docs/releases/index.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
2525
.. toctree::
2626
:maxdepth: 1
2727

28+
2.2.18
2829
2.2.17
2930
2.2.16
3031
2.2.15

tests/utils_tests/test_archive.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import tempfile
66
import unittest
77

8+
from django.core.exceptions import SuspiciousOperation
9+
from django.test import SimpleTestCase
810
from django.utils.archive import Archive, extract
911

1012
TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives')
@@ -87,3 +89,22 @@ class TestGzipTar(ArchiveTester, unittest.TestCase):
8789

8890
class TestBzip2Tar(ArchiveTester, unittest.TestCase):
8991
archive = 'foobar.tar.bz2'
92+
93+
94+
class TestArchiveInvalid(SimpleTestCase):
95+
def test_extract_function_traversal(self):
96+
archives_dir = os.path.join(os.path.dirname(__file__), 'traversal_archives')
97+
tests = [
98+
('traversal.tar', '..'),
99+
('traversal_absolute.tar', '/tmp/evil.py'),
100+
]
101+
if sys.platform == 'win32':
102+
tests += [
103+
('traversal_disk_win.tar', 'd:evil.py'),
104+
('traversal_disk_win.zip', 'd:evil.py'),
105+
]
106+
msg = "Archive contains invalid path: '%s'"
107+
for entry, invalid_path in tests:
108+
with self.subTest(entry), tempfile.TemporaryDirectory() as tmpdir:
109+
with self.assertRaisesMessage(SuspiciousOperation, msg % invalid_path):
110+
extract(os.path.join(archives_dir, entry), tmpdir)
10 KB
Binary file not shown.
10 KB
Binary file not shown.
10 KB
Binary file not shown.
312 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)