Composer: Downloading Random Code Is Not A Security Vulnerability?
Update: A fix which prevents Composer from locally installing packages not explicitly referred to by your root composer.json, or not explicitly referred to by your dependencies, has been committed to the Github repository: https://github.com/composer/composer/issues/2690. This change is live, so make sure to update your copy of Composer when possible. If you still experience any unexpected replaced/provided packages, be sure to open an issue.
A few days ago, I spotted a post floating past on Twitter entitled “Composer: Replace, Conflict & Forks Explained”. There has been some recent complaining about Composer downloading the wrong packages as dependencies due to its “replace” feature misbehaving, so seeing something hit the blogs was not unexpected. The issue has been encountered previously so having it reoccur caused some consternation. The article states:
Recently there has been an increase of cases in which Composer installs a fork of a package instead of the package the user expects. […] First of all, this behavior is not a security issue in Composer.
The problem here is quite simple. A user defines a composer.json file that requires the package bloggs/framework. Someone else creates a package on Packagist.org called evil/framework whose own composer.json states that it replaces bloggs/framework. Next, a group of poor random victims, potentially thousands, use composer to install applications with a dependency on bloggs/framework. Composer does some internal wizardry and installs evil/framework when certain conditions are met. The victims didn’t request evil/framework but they get it anyway.
If a piece of software, upon running, downloads files under the control of a potential attacker it is called a Remote File Inclusion (RFI) vulnerability. If the new file finds itself in a scope where it is executed, it’s also a Remote Code Execution (RCE) vulnerability. Two vulnerabilities for the price of one.
These definitions of two vulnerabilities apply to all software…except Composer. It’s possible that it’s not a security issue in Composer because it is in Packagist.org. That works too perhaps.
If your dependencies lead to a conflict with a package Composer may decide to install a fork instead which does not have the same conflict. If you notice that an unexpected fork is installed when running `composer update` you can debug the dependency problem that lead to the fork installation. Use the conflict key in your composer.json to blacklist the fork.
So don’t worry. This is all YOUR fault for not debugging, using conflict keys and assuming composer wouldn’t install potentially hostile code you never requested.
The mitigating manual checks described in the referenced blog post are not documented in the Composer manual as security concerns. This is the first time that these suggestions are being clearly communicated, so I assume many readers are not currently following them. They are also deeply flawed. Obviously, they are flawed because expecting people to do things that they never suspected were necessary is futile, but there’s also no attempt to elaborate on their assumed effectiveness.
Consider a scenario where an attacker decides that all this conflict blacklisting crap needs to be defeated for the few actually using blacklisting. They will immediately try two things:
- Flood Packagist.org with replacement packages. Try getting an attacker to voluntarily remove packages or have Packagist.org’s team manually weed them out. That may a) create frustration to end users if the attack succeeds, b) impose a denial of service on those trying to block illegal replaces, and c) destroy trust in Composer as a secure platform.
- They will not use obvious names. Would you trust something called fpotencier/twig? What about phpfig/log, synfony/console or laraval/framework? There are an infinite number of innocent looking and trustworthy package names to apply to the infinite number of false packages an attacker could setup. Longer names are easier to fudge and less likely to draw attention from the big frameworks unless they break CI builds.
We understand that Composer’s behaviour regarding forks and package replacements is unintuitive. So I’ve proposed a number of changes yet to be implemented to the handling of replace & provide on our issue tracker at https://github.com/composer/composer/issues/2690.
So, the unintuitive feature which is definitely not a security vulnerability is going to be changed. Will its ability to inject hostile code be documented? Will it have an optional off switch added to the command line options?
Only packages which match a dependency by name (ignoring replaces/providers) or a dependency of any potential dependency identified recursively by name will be considered for installation. The error reporting should further query for alternative replacers/providers which the user can require in the root package to satisfy the dependency.
The feature is being removed at some future unspecified time.
There’s more though. The Packagist.org folk are also manually deleting packages which are found to be replacing valid packages improperly and labeling it as an abuse of Packagist. If it’s an abuse – where are the Packagist.org automatic checks to prevent it? If it’s an abuse – how can it possibly be a “feature”? If it’s an abuse – isn’t that also another one of those things that are definitely not a security vulnerability?
Anyway I deleted the offending fork, @mlebkowski @nediam please take notice and use https://getcomposer.org/doc/05-repositories.md#vcs instead of abusing packagist to fork packages. If it’s a real fork you intend to maintain you can submit it to packagist but then you should not use replace and you should definitely add a note explaining what your version does differently.
Saying one thing, but acting like it’s the other thing you don’t want people to call it, makes me think it really is the other thing. Probably because it is. Users can fall victim to a replace and it’s called “unintuitive”, but if a package states that it replaces something that might lead to the unintuitive behaviour, it’s an abuse.
Packagist.org hosts somewhere just short of 25,000 packages. That almost beggars belief in a community barely out of the shadow of PEAR’s dominance. Composer has become an integral part of how we develop and distribute software. It simply doesn’t fit well with me that Composer can have security issues of this extent, over extended periods of time, and then see blog posts denying their very existence under the cover of “users are holding it wrong” while fixing them anyway in public Github issues for the whole world to see.
The reason why I absolutely despair at people denying the obvious is that it’s a bad habit to fall victim to. When you can deny a security vulnerability exists, even when it obviously does exist, you can justify not fixing it or undertaking endless bikeshedding. In this particular case, there’s finally pressure there to get something done on a problem that has continued occurring despite repeated complaints.
This is not the first time an issue has been openly discussed concerning Composer’s attitude to security. Composer is too intrinsically a part of PHP’s ecosystem to be allowed to take security issues so lightly. Since users are obviously exposed, this issue needs to be fixed as soon as possible. Preferably by yesterday.
In the meantime – readers should do as the referenced blog post says. The critical part is checking what you actually installed (I’m assuming dry runs followed by installs are susceptible to race conditions with Packagist.org’s update cycle), not running any installation scripts and being 100% certain that your composer.lock file is free of creative spelling errors.