VYPR
High severityNVD Advisory· Published Apr 13, 2022· Updated Apr 23, 2025

Missing input validation can lead to command execution in composer

CVE-2022-24828

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.

PackageAffected versionsPatched versions
composer/composerPackagist
< 1.10.261.10.26
composer/composerPackagist
>= 2.0, < 2.2.122.2.12
composer/composerPackagist
>= 2.3, < 2.3.52.3.5

Affected products

1

Patches

1
2c40c53637c5

Merge pull request from GHSA-x7cr-6qr6-2hh6

https://github.com/composer/composerStephanApr 13, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.