CVE-2022-3607: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Failure to Sanitize Special Elements into a Different Plane (Special Element Injection) in GitHub repository octoprint/octoprint prior to 1.8.3.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 6
Published: 2022-10-19T13:15Z
Repository: https://github.com/octoprint/octoprint
Language: Python
Files changed: src/octoprint/server/api/languages.py
Commit date: 2022-08-31 18:28:50 +0200
Commit message: 🔒️ Close a sanity check hole in language packs

A language pack containing a directory traversing
symlink could be uploaded. Upon backup creation, the
symlink would then lead to inclusion of whatever it
was pointing to in the resulting backup. This could
be used by an attacker with admin permissions to extract
information from the server.

Diff: diff --git a/src/octoprint/server/api/languages.py b/src/octoprint/server/api/languages.py
index 528bbce8fd..87945a60b2 100644
--- a/src/octoprint/server/api/languages.py
+++ b/src/octoprint/server/api/languages.py
@@ -172,27 +172,39 @@ def deleteInstalledLanguagePack(locale, pack):
 
 
 def _unpack_uploaded_zipfile(path, target):
     with zipfile.ZipFile(path, "r") as zip:
         # sanity check
-        map(_validate_archive_name, zip.namelist())
+        for info in zip.infolist():
+            _validate_zip_info(info, target)
 
         # unpack everything
         zip.extractall(target)
 
 
 def _unpack_uploaded_tarball(path, target):
     with tarfile.open(path, "r") as tar:
         # sanity check
-        map(_validate_archive_name, tar.getmembers())
+        for info in tar.getmembers():
+            _validate_tar_info(info, target)
 
         # unpack everything
         tar.extractall(target)
 
 
-def _validate_archive_name(name):
-    if name.startswith("/") or ".." in name:
+def _validate_archive_name(name, target):
+    if not os.path.abspath(os.path.join(target, name)).startswith(target + os.path.sep):
         raise InvalidLanguagePack(f"Provided language pack contains invalid name {name}")
 
 
+def _validate_zip_info(info, target):
+    _validate_archive_name(info.filename, target)
+
+
+def _validate_tar_info(info, target):
+    _validate_archive_name(info.name, target)
+    if not (info.isfile() or info.isdir()):
+        raise InvalidLanguagePack("Provided language pack contains invalid file type")
+
+
 class InvalidLanguagePack(Exception):
     pass

Vulnerable code:         map(_validate_archive_name, zip.namelist())
        map(_validate_archive_name, tar.getmembers())
def _validate_archive_name(name):
    if name.startswith("/") or ".." in name:
Fixed code:         for info in zip.infolist():
            _validate_zip_info(info, target)
        for info in tar.getmembers():
            _validate_tar_info(info, target)
def _validate_archive_name(name, target):
    if not os.path.abspath(os.path.join(target, name)).startswith(target + os.path.sep):
def _validate_zip_info(info, target):
    _validate_archive_name(info.filename, target)


def _validate_tar_info(info, target):
    _validate_archive_name(info.name, target)
    if not (info.isfile() or info.isdir()):
        raise InvalidLanguagePack("Provided language pack contains invalid file type")