Django · Report #3328367
Path traversal via archive.extract - CVE 2021-3281 incomplete patch
How it was found
Hi,
While performing a code review on previous vulnerability patches, I found that the fix for the CVE-2021-3281 is incomplete. Just as a reminder, in order to mitigate this vulnerability, the following guard logic has been added in the extract function of both ZipArchive and TarArchive classes to ensure that all files are extracted under the base folder (target_path).
# django/django/utils/archive.py
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
The abspath function is used to retrieve the absolute and normalized unique path. Then, the startswith function is leveraged to verify that the destination path is part of the intended base folder. However, the abspath function removes terminating path separators, which makes the guard logic protection insufficient to protect against partial-path traversal.
Assuming the base folder is defined as /var/lib/, and the initial file name as /var/library/test.txt. As the trailing slash is removed, an attacker could still partially path traversal to another directory starting with the same characters since this passes the check.
"/var/library/test.txt".startswith("/var/lib") == true
As an example, executing the following lines in a Python CLI does not raise the intended Exception.
import os
target_path = os.path.abspath("/var/lib/")
filename = os.path.abspath(os.path.join(target_path, "/var/library/test.txt"))
if not filename.startswith(target_path):
raise Exception
A real-life example of such a partial path traversal vulnerability can be found here.
Impact
This leads to a partial-path traversal vulnerability, which allows an attacker to write files outside of the base directory when unzipping archives (tar and zip).