CVE-2022-35861: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: pyenv 1.2.24 through 2.3.2 allows local users to gain privileges via a .python-version file in the current working directory. An attacker can craft a Python version string in .python-version to execute shims under their control. (Shims are executables that pass a command along to a specific version of pyenv. The version string is used to construct the path to the command, and there is no validation of whether the version specified is a valid version. Thus, relative path traversal can occur.)
CWE 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.
CVSS v3 base score: 7.8
Published: 2022-07-17T17:15Z
Repository: https://github.com/pyenv/pyenv
Language: Other
Files changed: libexec/pyenv-version-file-read, test/version-file-read.bats
Commit date: 2022-07-17 01:01:04 +0300
Commit message: CVE-2022-35861: Fixed relative path traversal due to using version string in path (#2412)


Diff: diff --git a/libexec/pyenv-version-file-read b/libexec/pyenv-version-file-read
index 5dcc40fc..faaf1596 100755
--- a/libexec/pyenv-version-file-read
+++ b/libexec/pyenv-version-file-read
@@ -9,13 +9,20 @@ if [ -s "$VERSION_FILE" ]; then
   # Read the first non-whitespace word from the specified version file.
   # Be careful not to load it whole in case there's something crazy in it.
   IFS="${IFS}"$'\r'
   sep=
   while read -n 1024 -r version _ || [[ $version ]]; do
-      [[ -z $version || $version == \#* ]] && continue
-      printf "%s%s" "$sep" "$version"
-      sep=:
+    if [[ -z $version || $version == \#* ]]; then
+      # Skip empty lines and comments
+      continue
+    elif [ "$version" = ".." ] || [[ $version == */* ]]; then
+      # The version string is used to construct a path and we skip dubious values.
+      # This prevents issues such as path traversal (CVE-2022-35861).
+      continue
+    fi
+    printf "%s%s" "$sep" "$version"
+    sep=:
   done <"$VERSION_FILE"
   [[ $sep ]] && { echo; exit; }
 fi
 
 exit 1
diff --git a/test/version-file-read.bats b/test/version-file-read.bats
index a7b184de..18cfe131 100644
--- a/test/version-file-read.bats
+++ b/test/version-file-read.bats
@@ -80,5 +80,17 @@ IN
 2.7.16
 IN
   run pyenv-version-file-read my-version
   assert_success "3.9.3:3.8.9:2.7.16"
 }
+
+@test "skips relative path traversal" {
+  cat > my-version <<IN
+3.9.3
+3.8.9
+  ..
+./*
+2.7.16
+IN
+  run pyenv-version-file-read my-version
+  assert_success "3.9.3:3.8.9:2.7.16"
+}

Vulnerable code:       [[ -z $version || $version == \#* ]] && continue
      printf "%s%s" "$sep" "$version"
      sep=:
Fixed code:     if [[ -z $version || $version == \#* ]]; then
      # Skip empty lines and comments
      continue
    elif [ "$version" = ".." ] || [[ $version == */* ]]; then
      # The version string is used to construct a path and we skip dubious values.
      # This prevents issues such as path traversal (CVE-2022-35861).
      continue
    fi
    printf "%s%s" "$sep" "$version"
    sep=:

@test "skips relative path traversal" {
  cat > my-version <<IN
3.9.3
3.8.9
  ..
./*
2.7.16
IN
  run pyenv-version-file-read my-version
  assert_success "3.9.3:3.8.9:2.7.16"
}