Missing input validation can lead to command execution in composer
Description
Composer is a dependency manager for the PHP programming language. Integrators using Composer code to call VcsDriver::getFileContent can have a code injection vulnerability if the user can control the $file or $identifier argument. This leads to a vulnerability on packagist.org for example where the composer.json's readme field can be used as a vector for injecting parameters into hg/Mercurial via the $file argument, or git via the $identifier argument if you allow arbitrary data there (Packagist does not, but maybe other integrators do). Composer itself should not be affected by the vulnerability as it does not call getFileContent with arbitrary data into $file/$identifier. To the best of our knowledge this was not abused, and the vulnerability has been patched on packagist.org and Private Packagist within a day of the vulnerability report.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
composer/composerPackagist | < 1.10.26 | 1.10.26 |
composer/composerPackagist | >= 2.0, < 2.2.12 | 2.2.12 |
composer/composerPackagist | >= 2.3, < 2.3.5 | 2.3.5 |
Affected products
1Patches
12c40c53637c5Merge pull request from GHSA-x7cr-6qr6-2hh6
4 files changed · +139 −4
src/Composer/Repository/Vcs/GitDriver.php+5 −1 modified@@ -138,6 +138,10 @@ public function getDist($identifier) */ public function getFileContent($file, $identifier) { + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + $resource = sprintf('%s:%s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute(sprintf('git show %s', $resource), $content, $this->repoDir); @@ -191,7 +195,7 @@ public function getBranches() $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { if ($branch && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) { - if (Preg::isMatch('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) { + if (Preg::isMatch('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } }
src/Composer/Repository/Vcs/HgDriver.php+7 −3 modified@@ -126,7 +126,11 @@ public function getDist($identifier) */ public function getFileContent($file, $identifier) { - $resource = sprintf('hg cat -r %s %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); + if (isset($identifier[0]) && $identifier[0] === '-') { + throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier); + } + + $resource = sprintf('hg cat -r %s -- %s', ProcessExecutor::escape($identifier), ProcessExecutor::escape($file)); $this->process->execute($resource, $content, $this->repoDir); if (!trim($content)) { @@ -186,14 +190,14 @@ public function getBranches() $this->process->execute('hg branches', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { - if ($branch && Preg::isMatch('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match)) { + if ($branch && Preg::isMatch('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') { $branches[$match[1]] = $match[2]; } } $this->process->execute('hg bookmarks', $output, $this->repoDir); foreach ($this->process->splitLines($output) as $branch) { - if ($branch && Preg::isMatch('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match)) { + if ($branch && Preg::isMatch('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') { $bookmarks[$match[1]] = $match[2]; } }
tests/Composer/Test/Repository/Vcs/GitDriverTest.php+81 −0 added@@ -0,0 +1,81 @@ +<?php + +namespace Composer\Test\Repository\Vcs; + +use Composer\Config; +use Composer\Repository\Vcs\GitDriver; +use Composer\Test\Mock\ProcessExecutorMock; +use Composer\Test\TestCase; + +class GitDriverTest extends TestCase +{ + /** @var Config */ + private $config; + /** @var string */ + private $home; + + public function setUp() + { + $this->home = self::getUniqueTmpDirectory(); + $this->config = new Config(); + $this->config->merge(array( + 'config' => array( + 'home' => $this->home, + ), + )); + } + + public function testGetBranchesFilterInvalidBranchNames() + { + $process = new ProcessExecutorMock; + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + + $driver = new GitDriver(array('url' => 'https://example.org/acme.git'), $io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); + $this->setRepoDir($driver, $this->home); + + // Branches starting with a - character are not valid git branches names + // Still assert that they get filtered to prevent issues later on + $stdout = <<<GIT +* main 089681446ba44d6d9004350192486f2ceb4eaa06 commit + 2.2 12681446ba44d6d9004350192486f2ceb4eaa06 commit + -h 089681446ba44d6d9004350192486f2ceb4eaa06 commit +GIT; + + $process + ->expects(array(array( + 'cmd' => 'git branch --no-color --no-abbrev -v', + 'stdout' => $stdout, + ))); + + $branches = $driver->getBranches(); + $this->assertSame(array( + 'main' => '089681446ba44d6d9004350192486f2ceb4eaa06', + '2.2' => '12681446ba44d6d9004350192486f2ceb4eaa06', + ), $branches); + } + + public function testFileGetContentInvalidIdentifier() + { + $this->expectException('\RuntimeException'); + + $process = new ProcessExecutorMock; + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $driver = new GitDriver(array('url' => 'https://example.org/acme.git'), $io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); + + $this->assertNull($driver->getFileContent('file.txt', 'h')); + + $driver->getFileContent('file.txt', '-h'); + } + + /** + * @param GitDriver $driver + * @param string $path + */ + private function setRepoDir($driver, $path) + { + $reflectionClass = new \ReflectionClass($driver); + $reflectionProperty = $reflectionClass->getProperty('repoDir'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($driver, $path); + } +}
tests/Composer/Test/Repository/Vcs/HgDriverTest.php+46 −0 modified@@ -13,6 +13,7 @@ namespace Composer\Test\Repository\Vcs; use Composer\Repository\Vcs\HgDriver; +use Composer\Test\Mock\ProcessExecutorMock; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Config; @@ -66,4 +67,49 @@ public function supportsDataProvider() array('https://user@bitbucket.org/user/repo'), ); } + + public function testGetBranchesFilterInvalidBranchNames() + { + $process = new ProcessExecutorMock; + + $driver = new HgDriver(array('url' => 'https://example.org/acme.git'), $this->io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); + + $stdout = <<<HG_BRANCHES +default 1:dbf6c8acb640 +--help 1:dbf6c8acb640 +HG_BRANCHES; + + $stdout1 = <<<HG_BOOKMARKS +help 1:dbf6c8acb641 +--help 1:dbf6c8acb641 + +HG_BOOKMARKS; + + $process + ->expects(array(array( + 'cmd' => 'hg branches', + 'stdout' => $stdout, + ), array( + 'cmd' => 'hg bookmarks', + 'stdout' => $stdout1, + ))); + + $branches = $driver->getBranches(); + $this->assertSame(array( + 'help' => 'dbf6c8acb641', + 'default' => 'dbf6c8acb640', + ), $branches); + } + + public function testFileGetContentInvalidIdentifier() + { + $this->expectException('\RuntimeException'); + + $process = new ProcessExecutorMock; + $driver = new HgDriver(array('url' => 'https://example.org/acme.git'), $this->io, $this->config, $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), $process); + + $this->assertNull($driver->getFileContent('file.txt', 'h')); + + $driver->getFileContent('file.txt', '-h'); + } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-x7cr-6qr6-2hh6ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/625MT3IKWKFVIWLSYZFSXHVUA2LES7YQ/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/GWT6LDSRY7SFMTDZWJ4MS2ZBXHL7VQEF/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/QD7JQWL6C4GVROO25DTXWYWM6BPOPPCG/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2022-24828ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/composer/composer/CVE-2022-24828.yamlghsaWEB
- github.com/composer/composer/commit/2c40c53637c5c7e43fff7c09d3d324d632734709ghsax_refsource_MISCWEB
- github.com/composer/composer/security/advisories/GHSA-x7cr-6qr6-2hh6ghsax_refsource_CONFIRMWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/625MT3IKWKFVIWLSYZFSXHVUA2LES7YQghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GWT6LDSRY7SFMTDZWJ4MS2ZBXHL7VQEFghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/QD7JQWL6C4GVROO25DTXWYWM6BPOPPCGghsaWEB
- www.tenable.com/security/tns-2022-09ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.