CVE-2024-35242: Insufficient Information (NVD-CWE-noinfo)
value: Composer is a dependency manager for PHP. On the 2.x branch prior to versions 2.2.24 and 2.7.7, the `composer install` command running inside a git/hg repository which has specially crafted branch names can lead to command injection. This requires cloning untrusted repositories. Patches are available in version 2.2.24 for 2.2 LTS or 2.7.7 for mainline. As a workaround, avoid cloning potentially compromised repositories.

CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-06-10T22:15Z
Repository: https://github.com/composer/composer
Language: PHP
Files changed: src/Composer/Package/Version/VersionGuesser.php, tests/Composer/Test/Package/Version/VersionGuesserTest.php
Commit date: 2024-06-10 14:56:13 +0200
Commit message: Merge pull request from GHSA-v9qv-c7wm-wgmf


Diff: diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php
index 2b2b197065..72fc799a70 100644
--- a/src/Composer/Package/Version/VersionGuesser.php
+++ b/src/Composer/Package/Version/VersionGuesser.php
@@ -171,11 +171,11 @@ private function guessGitVersion(array $packageConfig, string $path): array
             if ($isFeatureBranch) {
                 $featureVersion = $version;
                 $featurePrettyVersion = $prettyVersion;
 
                 // try to find the best (nearest) version branch to assume this feature's version
-                $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path);
+                $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path);
                 $version = $result['version'];
                 $prettyVersion = $result['pretty_version'];
             }
         }
 
@@ -246,11 +246,11 @@ private function guessHgVersion(array $packageConfig, string $path): ?array
             $io = new NullIO();
             $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process);
             $branches = array_map('strval', array_keys($driver->getBranches()));
 
             // try to find the best (nearest) version branch to assume this feature's version
-            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path);
+            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path);
             $result['commit'] = '';
             $result['feature_version'] = $version;
             $result['feature_pretty_version'] = $version;
 
             return $result;
@@ -259,17 +259,16 @@ private function guessHgVersion(array $packageConfig, string $path): ?array
         return null;
     }
 
     /**
      * @param array<string, mixed>     $packageConfig
-     * @param string[]                 $branches
-     *
-     * @phpstan-param non-empty-string $scmCmdline
+     * @param list<string>             $branches
+     * @param list<string>             $scmCmdline
      *
      * @return array{version: string|null, pretty_version: string|null}
      */
-    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, string $scmCmdline, string $path): array
+    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array
     {
         $prettyVersion = $version;
 
         // ignore feature branches if they have no branch-alias or self.version is used
         // and find the branch they came from to use as a version instead
@@ -307,11 +306,13 @@ private function guessFeatureVersion(array $packageConfig, ?string $version, arr
                     // do not compare against itself or other feature branches
                     if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) {
                         continue;
                     }
 
-                    $cmdLine = str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $scmCmdline);
+                    $cmdLine = array_map(static function (string $component) use ($candidate, $branch) {
+                        return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component);
+                    }, $scmCmdline);
                     $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void {
                         if (!$process->isSuccessful()) {
                             return;
                         }
 
diff --git a/tests/Composer/Test/Package/Version/VersionGuesserTest.php b/tests/Composer/Test/Package/Version/VersionGuesserTest.php
index 676ef24202..c6e01251b3 100644
--- a/tests/Composer/Test/Package/Version/VersionGuesserTest.php
+++ b/tests/Composer/Test/Package/Version/VersionGuesserTest.php
@@ -115,11 +115,11 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF
             [
                 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
                 'stdout' => "  arbitrary $commitHash Commit message\n* feature $anotherCommitHash Another message\n",
             ],
             [
-                'cmd' => 'git rev-list arbitrary..feature',
+                'cmd' => ['git', 'rev-list', 'arbitrary..feature'],
                 'stdout' => "$anotherCommitHash\n",
             ],
         ], true);
 
         $config = new Config;
@@ -145,11 +145,11 @@ public function testGuessVersionReadsAndRespectsNonFeatureBranchesConfigurationF
             [
                 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
                 'stdout' => "  latest-testing $commitHash Commit message\n* feature $anotherCommitHash Another message\n",
             ],
             [
-                'cmd' => 'git rev-list latest-testing..feature',
+                'cmd' => ['git', 'rev-list', 'latest-testing..feature'],
                 'stdout' => "$anotherCommitHash\n",
             ],
         ], true);
 
         $config = new Config;
@@ -350,11 +350,11 @@ public function testRemoteBranchesAreSelected(): void
                 'cmd' => ['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'],
                 'stdout' => "* feature-branch 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n".
                         "remotes/origin/1.5 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n",
             ],
             [
-                'cmd' => 'git rev-list remotes/origin/1.5..feature-branch',
+                'cmd' => ['git', 'rev-list', 'remotes/origin/1.5..feature-branch'],
                 'stdout' => "\n",
             ],
         ], true);
 
         $config = new Config;

Vulnerable code:                 $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'git rev-list %candidate%..%branch%', $path);
            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"', $path);
     * @param string[]                 $branches
     *
     * @phpstan-param non-empty-string $scmCmdline
    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, string $scmCmdline, string $path): array
                    $cmdLine = str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $scmCmdline);
                'cmd' => 'git rev-list arbitrary..feature',
                'cmd' => 'git rev-list latest-testing..feature',
                'cmd' => 'git rev-list remotes/origin/1.5..feature-branch',
Fixed code:                 $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path);
            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path);
     * @param list<string>             $branches
     * @param list<string>             $scmCmdline
    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array
                    $cmdLine = array_map(static function (string $component) use ($candidate, $branch) {
                        return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component);
                    }, $scmCmdline);
                'cmd' => ['git', 'rev-list', 'arbitrary..feature'],
                'cmd' => ['git', 'rev-list', 'latest-testing..feature'],
                'cmd' => ['git', 'rev-list', 'remotes/origin/1.5..feature-branch'],