When it comes to PHP, composer is without discussion, THE package manager. It’s fast, easy to use, actively maintained and very secure — or so most thought.
On April 21, 2021, a command injection vulnerability was reported, which shook the PHP community. Fortunately it didn’t have a very big impact, but it could have. The problem with the vulnerability is that it affected the very heart of the Composer supply chain: Packagist servers.
If this issue hadn’t been detected on time, the consequences could have been extremely severe, since PHP supports over 80% of the web and Composer is used in most of them.
The fact that Packagist’s servers were compromised could have allowed the leakage of maintainers’ credentials or the redirection of downloads to malicious servers, causing behavior that’s hard to predict, such as distribution of worms, backdoors, etc…
The good news is that the maintainers of Composer and Packagist responded right away and the problem was fixed within 12 hours. Currently there’s no sign of the vulnerability being exploited.
There’s one ugly side to this though: while most php based projects use the public repository (packagist.org), there’s also the possibility of hosting it privately. Should that be the case of a private organization, it’s clearly beyond Packagist’s maintainers to apply the fix there.
In order to understand this you need to know a little bit about the inner workings of composer:
Composer is a php script that must be present in every server hosting a php project that relies on it for dependency management. Upon deployment of a new version of an application, dependencies must be fetched from wherever they live.
In practice this means invoking a composer command — some variation of composer install. When composer is asked to install a new package, it contacts a repository (most likely https://packagist.org) to look for metadata about the library being incorporated into the project.
One very important piece of this metadata is the URL to the library’s code, which is to be downloaded and stored locally inside the vendor/ sub-directory.
This URL typically points to a CVS, for instance:
This json structure is returned from Packagist upon request to include the library abdielcs/expanded-collection-bundle.
Among other things, it’s telling the local Composer installation to checkout from the repository found in the URL that the property url points to, in this case: https://github.com/abdielcs/ExpandedCollectionBundle.git.
As you can see, Packagist is just a meta-data repository, the actual hosting of the libraries’ code is the responsibility of each individual maintainer. So, in a way, Packagist serves as a huge directory, translating from a package name to a download URL.
Since Packagist is a public directory, anyone can upload a new package to it with very little effort — basically anyone with an email address can do it. That’s exactly what allowed its growth and popularity. But at the same time, it resulted in this type of vulnerability. .
The problem is Packagist will not perform many validations other than the json structure being correct, which allows placing something other than a URL within the “source” definition. Of course the Composer script does some sanitization on URLs, but at the end of the day, this information is fed straight to external VCS commands, creating the opportunity for exploitation.
The malicious command injection is done through unvalidated options, like this:
return ‘git ls-remote –heads ‘ . ProcessExecutor::escape($url);
In this case git is called as an operating system command and the external input ($url) is properly sanitized.
But what if the url started with “–”?
In this case the actual command being called would not be exactly what the developers intended — and that’s where things get tricky.
When Packagist finds a new package it will immediately query the repository URL to get some basic information and make it available to everyone. This means that if the URL contains malicious code, it will be executed at Packagist servers, which is exactly what the researcher Thomas Chauchefoin did.
Using a very well crafted source URL, combined with a not-so-well-known feature of Mercurial, Thomas and his team were able to execute an arbitrary command at packagist servers, effectively proving the feasibility of it. For a full technical detail check the report done by the researcher who found the issue.
One interesting fact about this vulnerability is that it was introduced over 10 years before it was found!
The fact that it went undetected for so long is an indicator of the obscurity of this type of threat — parameter injection. But as usual, the best way to prevent this from happening is to avoid the execution of external commands created from strings containing user provided input.
In this particular case though, it’s not exactly clear whether this is even possible since one of the goals of Composer is to be VCS (Version Control System) independent — so there might not be native php implementations of each one, given the multitude of VCSs.
The increased complexity of the software development process makes it extremely hard to cover every possible base in the open source supply chain. If your projects are of any considerable size, they’ll most certainly depend on third-party components that can be compromised as well. The Composer vulnerability is an example of how the very tools used to provision such dependencies can also be subject to attacks.
Your best chance of staying on the safe side is to rely on automated tools to monitor and alert about security vulnerabilities as soon as possible, one such tool is Mend Supply Chain Defender.