CVE-2022-4885 (published 2023-01-11T18:15Z)

A vulnerability has been found in sviehb jefferson up to 0.3 and classified as critical. This vulnerability affects unknown code of the file src/scripts/jefferson. The manipulation leads to path traversal. The attack can be initiated remotely. The complexity of an attack is rather high. The exploitation appears to be difficult. Upgrading to version 0.4 is able to address this issue. The name of the patch is 53b3f2fc34af0bb32afbcee29d18213e61471d87. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-218020.

CVSS v3 base score: 5.9, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/sviehb/jefferson
commit_details: repo_url: https://github.com/sviehb/jefferson
author: Quentin Kaiser
author_date: 2022-01-24 12:31:45+01:00
msg: Fix path traversal security vulnerability by canonicalizing path names of every inodes and discarding inodes with a path
pointing outside of the extraction directory.

Fix path traversal through symlinks by canonicalizing link target path using the extraction directory as root. If the link
still points outside the extraction root, it gets discarded. This way symlinks reflects the reality of a filesystem on device
by pointing to files within the extracted filesystem instead of files from the host executing jefferson.
repository: repo_url: https://github.com/sviehb/jefferson
repo_name: sviehb/jefferson
description: JFFS2 filesystem extraction tool
repo_language: Python
owner: sviehb
file_changes: filename: jefferson
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,6 @@ import stat
 import os
 import zlib
 import binascii
-
 import cstruct
 
 from jefferson import jffs2_lzma, rtime
@@ -52,6 +51,12 @@ JFFS2_NODETYPE_XREF = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 9
 def mtd_crc(data):
     return (binascii.crc32(data, -1) ^ -1) & 0xFFFFFFFF
 
+def is_safe_path(basedir, path, follow_symlinks=True):
+    if follow_symlinks:
+        matchpath = os.path.realpath(path)
+    else:
+        matchpath = os.path.abspath(path)
+    return basedir == os.path.commonpath((basedir, matchpath))
 
 cstruct.typedef("uint8", "uint8_t")
 cstruct.typedef("uint16", "jint16_t")
@@ -474,7 +479,12 @@ def dump_fs(fs, target):
         node_names.append(dirent.name.decode())
         path = "/".join(node_names)
 
-        target_path = os.path.join(os.getcwd(), target, path)
+        target_path = os.path.realpath(os.path.join(os.getcwd(), target, path))
+
+        if not is_safe_path(target, target_path):
+            print(f"Path traversal attempt to {target_path}, discarding.")
+            continue
+
         for inode in dirent.inodes:
             try:
                 if stat.S_ISDIR(inode.mode):
@@ -482,12 +492,19 @@ def dump_fs(fs, target):
                     if not os.path.isdir(target_path):
                         os.makedirs(target_path)
                 elif stat.S_ISLNK(inode.mode):
+                    link_path = inode.data.decode('utf-8')
+                    if link_path:
+                        link_path = link_path[1:]
+                    link_target = os.path.realpath(os.path.join(target, link_path))
+                    if not is_safe_path(target, link_target):
+                        print(f"Path traversal attempt through symlink to {link_target}, discarding.")
+                        continue
                     print("writing S_ISLNK", path)
                     if not os.path.islink(target_path):
                         if os.path.exists(target_path):
                             print("file already exists as", inode.data)
                             continue
-                        os.symlink(inode.data, target_path)
+                        os.symlink(link_target, target_path)
                 elif stat.S_ISREG(inode.mode):
                     print("writing S_ISREG", path)
                     if not os.path.isfile(target_path):
@@ -561,7 +578,7 @@ def main():
         if not fs[JFFS2_NODETYPE_DIRENT]:
             continue
 
-        dest_path_fs = os.path.join(dest_path, "fs_%i" % fs_index)
+        dest_path_fs = os.path.realpath(os.path.join(dest_path, "fs_%i" % fs_index))
         print("dumping fs #%i to %s" % (fs_index, dest_path_fs))
         for key, value in fs.items():
             if key == "endianness":

programming_language: Python