VYPR
High severityNVD Advisory· Published Oct 5, 2021· Updated Aug 4, 2024

Command injection in composer on Windows

CVE-2021-41116

Description

Composer is an open source dependency manager for the PHP language. In affected versions windows users running Composer to install untrusted dependencies are subject to command injection and should upgrade their composer version. Other OSs and WSL are not affected. The issue has been resolved in composer versions 1.10.23 and 2.1.9. There are no workarounds for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
composer/composerPackagist
< 1.10.231.10.23
composer/composerPackagist
>= 2.0.0-alpha1, < 2.1.92.1.9

Affected products

1

Patches

1
ca5e2f8d505f

Fix escaping issues on Windows which could lead to command injection, fixes GHSA-frqg-7g38-6gcf

https://github.com/composer/composerJordi BoggianoOct 5, 2021via ghsa
5 files changed · +22 36
  • CHANGELOG.md+5 0 modified
    @@ -1,3 +1,7 @@
    +### [1.10.23] 2021-10-05
    +
    +  * Security: Fixed command injection vulnerability on Windows (GHSA-frqg-7g38-6gcf / CVE-2021-41116)
    +
     ### [1.10.22] 2021-04-27
     
       * Security: Fixed command injection vulnerability in HgDriver/HgDownloader and hardened other VCS drivers and downloaders (GHSA-h5h8-pc6h-jvvx / CVE-2021-29472)
    @@ -938,6 +942,7 @@
     
       * Initial release
     
    +[1.10.23]: https://github.com/composer/composer/compare/1.10.22...1.10.23
     [1.10.22]: https://github.com/composer/composer/compare/1.10.21...1.10.22
     [1.10.21]: https://github.com/composer/composer/compare/1.10.20...1.10.21
     [1.10.20]: https://github.com/composer/composer/compare/1.10.19...1.10.20
    
  • src/Composer/Command/HomeCommand.php+1 1 modified
    @@ -129,7 +129,7 @@ private function openBrowser($url)
     
             $process = new ProcessExecutor($this->getIO());
             if (Platform::isWindows()) {
    -            return $process->execute('start "web" explorer "' . $url . '"', $output);
    +            return $process->execute('start "web" explorer ' . $url, $output);
             }
     
             $linux = $process->execute('which xdg-open', $output);
    
  • src/Composer/Util/ProcessExecutor.php+14 33 modified
    @@ -155,48 +155,29 @@ public static function escape($argument)
         }
     
         /**
    -     * Copy of ProcessUtils::escapeArgument() that is deprecated in Symfony 3.3 and removed in Symfony 4.
    +     * Copy of Symfony's Process::escapeArgument() which is private
          *
          * @param string $argument
          *
          * @return string
          */
         private static function escapeArgument($argument)
         {
    -        //Fix for PHP bug #43784 escapeshellarg removes % from given string
    -        //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
    -        //@see https://bugs.php.net/bug.php?id=43784
    -        //@see https://bugs.php.net/bug.php?id=49446
    -        if ('\\' === DIRECTORY_SEPARATOR) {
    -            if ((string) $argument === '') {
    -                return escapeshellarg($argument);
    -            }
    -
    -            $escapedArgument = '';
    -            $quote = false;
    -            foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
    -                if ('"' === $part) {
    -                    $escapedArgument .= '\\"';
    -                } elseif (self::isSurroundedBy($part, '%')) {
    -                    // Avoid environment variable expansion
    -                    $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
    -                } else {
    -                    // escape trailing backslash
    -                    if ('\\' === substr($part, -1)) {
    -                        $part .= '\\';
    -                    }
    -                    $quote = true;
    -                    $escapedArgument .= $part;
    -                }
    -            }
    -            if ($quote) {
    -                $escapedArgument = '"'.$escapedArgument.'"';
    -            }
    -
    -            return $escapedArgument;
    +        if ('' === $argument || null === $argument) {
    +            return '""';
    +        }
    +        if ('\\' !== \DIRECTORY_SEPARATOR) {
    +            return "'".str_replace("'", "'\\''", $argument)."'";
    +        }
    +        if (false !== strpos($argument, "\0")) {
    +            $argument = str_replace("\0", '?', $argument);
    +        }
    +        if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
    +            return $argument;
             }
    +        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
     
    -        return "'".str_replace("'", "'\\''", $argument)."'";
    +        return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"';
         }
     
         private static function isSurroundedBy($arg, $char)
    
  • tests/Composer/Test/Downloader/GitDownloaderTest.php+1 1 modified
    @@ -524,7 +524,7 @@ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     
         public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
         {
    -        $expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer -- '' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- ''");
    +        $expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer -- \"\" && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- \"\"");
             $expectedSecondGitUpdateCommand = $this->winCompat("(git remote set-url composer -- 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- 'https://github.com/composer/composer'");
     
             $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
    
  • tests/Composer/Test/Util/SvnTest.php+1 1 modified
    @@ -47,7 +47,7 @@ public function urlProvider()
             return array(
                 array('http://till:test@svn.example.org/', $this->getCmd(" --username 'till' --password 'test' ")),
                 array('http://svn.apache.org/', ''),
    -            array('svn://johndoe@example.org', $this->getCmd(" --username 'johndoe' --password '' ")),
    +            array('svn://johndoe@example.org', $this->getCmd(" --username 'johndoe' --password \"\" ")),
             );
         }
     
    

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

8

News mentions

0

No linked articles in our index yet.