Composer is wide open with a massive security vulnerability

Update: bug is now fixed. See my new post

There’s been a bit of an uproar in the php world. The short version is that composer has a replace feature, that allows pretty much anyone to ‘replace’ a package of someone else.

The result is, that a simple composer update can allow an attacker to execute code on your machine. Either by using post-install-cmd or by replacing things in packages that get executed after the fact.

Pádraic Brady has more details. Largly as a response to this post by naderman.

The response

Getting a 0-day security vulnerability can happen to the best of us. What’s worrying is that the problem is completely ignored by the developers, and dismissed as: a feature, not a bug.

Well… I can’t argue that. It’s certainly a feature and there are legit reasons why the feature exists. Just like there are legit reasons to give everyone write access to my github repositories so it’s easier to contribute.

An unaltered comment from Jordi Boggiano, composer’s lead developer:

I’m not going to argue with the fact that it sucks that replace can be abused. But I don’t think you present the full picture (and I assume it’s due to a misunderstanding). First of all we did take steps in the past to mitigate this already: when multiple replacer packages match the ones of a similar vendor namespace will be preferred.

If we take for example zendframework/zendframework dev-develop, it replaces zendframework/zend-barcode dev-develop. So if you request zendframework/zend-barcode dev-develop and it didn’t exist, you would get zendframework/zendframework, even if foo/bar replaces it as well. The problem now is if you require the barcode package in dev-bonkers for example, then none of the versions of zendframework/zendframework replace dev-bonkers, so if foo/bar says it replaces * it will win in this case and be installed.

So to sum it up:

  • The replace feature is legit, and necessary, and used by many high profile packages. We can’t just remove it, and we can’t block it entirely. Yet when someone marks their forked package as replacing another one, it is an abuse of the feature since they don’t replace the same code. We can’t easily check this automatically though. The only way to improve it on packagist’s side is to restrict replace to same vendors for example, but that would also require that people can own a vendor namespace. Those would be two nice features, but they take time to implement, and we all only have so much of that.
  • Unless we introduced a regression, the only way to get some code injected in this way is if your requirements are invalid and some abusive package has overly-greedy replace constraints. The simple fact that someone adds an exploit package would not lead to all users of the replaced package to be vulnerable.
  • It’s overall a bad situation, and we are doing what we can to improve things. But replace itself is not a security vulnerability, it’s a feature.

To me this is an absolute crazy and irresponsible way to deal with this issue.

The two things that must happen now:

  1. Turn off this feature completely, until it’s fixed. Install a whitelist if you’re worried about the response, but close the hole first.
  2. Add a system to packagist that forces the original package to give explicit permission for another package to replace it.

Workarounds

The workaround explained by naderman is unsufficient, because it will only protect you against known package replacements.

The best thing I can imagine you can do, is to only run composer update in an isolated vm to first ensure there’s no fake package replacements.

Identifying those can be hard as well.. when is something legit, and when is it not? Do we manually go through the (potentially massive) list of packages and compare it with what we expect to see on github?

Web mentions

Comments

  • Gerard van Helden

    Better even, don't use packagist and host all packages privately, so you're in full control and can cherrypick. It's a huge performance increase as well because most of composers performance troubles come from dependency resolution.

    To be fair, the trouble is not the replace feature, it's packagist.

  • kix

    The right thing to do here and now is maybe to host own Satis repository. +1, @gerardvanhelden:disqus

  • Jeremy Kendall

    Important note: It's not accurate to say that post-install-cmd (or the rest of the scripts) are included in this (very serious) vulnerability. Composer's scripts are root-only: Composer will not execute any scripts defined in your dependencies (https://getcomposer.org/doc....

    UPDATE: On further reflection, I'm not precisely correct. If you use another project as the basis for your own and that other project includes a malicious Composer script, then the RCE and RCI vulnerabilities exist. That's different than a dependency being able to run a Composer script, however, and that was the basis of my comment above.

    • Evert

      Evert

      I removed the bit about post-install-cmd. Thanks!

  • willdurand

    Evert, this is not helpful...

    • Evert

      Evert

      Hey Will,

      I'm making an attempt to force the composer to take this issue seriously rather than sticking his head into the sand.

      Given that there was no appropriate response to this at all, I felt compelled to make this blogpost. It's time to deal with this issue in a responsible matter.

  • jwage

    We use satis and mirrors of GitHub repos. So when I want to update or add a dependency I have to update our mirrors and pull in the code I want. Then the composer update is ran on local developers virtual machine to update the composer.lock. It seems to me the only way this would be an issue is if you were using composer in an irresponsible/wrong way. That does not mean this isn't an issue. Just thought I'd share how this is not an issue specifically for us to give a little more color to others reading.