I’ve been spending a chunk of free time recently working on a few PRs for Composer related to security so this is my usual “let’s watch Paddy think aloud in a completely unstructured manner” blog post. But seriously, with all the issues and PRs going around, this is my detailed look at solving a “simple” problem: establishing trust in Composer’s source code and its operation thereafter.
The Composer issue, as initially reported by Kevin McArthur, was fairly simple. Since no download connection by Composer was properly secured using SSL/TLS then an attacker could, with the assistance of a Man-In-The-Middle (MITM) attack, substitute the package you wanted to download with a modified version that communicated with the attacker’s server. They could, for example, plant a line of code which sends the contents of $_POST to the attacker’s server.
The obvious solution is to implement TLS support…
To mitigate this risk, I updated Composer in a PR in the following ways:
- Peer verification is enabled by default. Disabling it nets you a continual warning message.
- It follows all recommended TLS options being introduced for PHP 5.6 (thanks to @rdlowrey).
- Since peer verification requires root CA certificates, Composer will attempt to locate a local system certificate bundle (thanks to @EvanDotPro).
- If all else fails, Composer bundles root CA certificates which it will fall back to.
- Users can override the default detected certificate bundle by manually setting a –cafile option for most commands.
- The Installer has also been updated for 1-5.
Composer should now operate with SSL/TLS protections out of the box. There may be edge cases since support for Subject Alternative Names (SANs) in SSL certs has not yet been added to PHP 5.4 or 5.5, but I’m hoping that future releases of these versions will see it added. This particular issue does not impact Packagist.
There are other TLS related features that can be looked into for the future. Users may want to generate their own CA cert bundle file as a substitute, e.g. Evan Coury’s Sslurp, to avoid a single point of failure or trust. With the dawning realisation that government surveillance is commonplace, and that trusted CA’s may mistakenly issue, or allow to be issued, certificates for entities without that entity’s permission, public certificate pinning may also warrant future attention.
SSL/TLS should protect the TCP connections between the client and the server, but it doesn’t actually verify that the code being downloaded was published by a trusted maintainer – only that you downloaded it from a verified host. So, what if the server were compromised? What if the SSL/TLS connection were breached?
Throughout 2013/2014, TLS has been besieged by a number of problems:
- Weaknesses in the protocol: SSL/TLS is made up of SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1 and TLS 1.2. Newer versions tend to be stronger, and SSL is overdue to be phased out of existence. Aside from obsolescence, the protocols are constantly under the microscope. On 4 March, researchers released details for the new “Triple Handshake Attack”. You’re probably already familiar with terms like CRIME and BEAST from 2013. Another part of the protocol is how encryption is implemented. TLS may use quite a number of cipher suites (named sets of authentication, encryption and MAC algos) in any particular order of preference. Reordering the preference to favour stronger ciphers which have Perfect Forward Secrecy (PFS) as an attribute is essential to mitigate against the loss of private keys (genuine loss, stolen, demanded by a court order or NSL). Without PFS, one could decrypt previously logged requests once they had the private key. PFS is not favoured by default in openssl or curl, but it is as part of the PHP 5.6 overhaul in PHP streams.
- Weaknesses in the Certificate Authority: CAs, like any entity, are capable of mistakes. In the past, some CAs have mistakenly released, or had stolen, trusted certificates which were used to create new certificates to impersonate entities like Google or to sign software, e.g. as Adobe. This is one reason why it is essential for any software bundling CA certificates to have a process for updating them regularly. There have been lots of CA problems. For example, in 2012, Trustwave publicised a worrying practice among CAs. Trustwave had been selling hardware implanted with subordinate certificates for the specific purposes of generating keys for domains that their customers did not necessarily own, and they insinuated that other CAs had similar businesses. One can imagine what the NSA would do with such a machine.
- Weakness in implementation: Apple screwed it up big time in OS X and iOS. GnuTLS was just patched for a similar issue and I was installing updates yesterday. PHP’s native SSL defaults are both insecure and buggy, to be fixed primarily in PHP 5.6. The NSA was reading unsecured intra-datacentre traffic from Google, Yahoo, Microsoft and probably most of the planet beyond those three. Companies like to monitor what employees send over HTTPS (if you work for one, check your company browser to see what certificates are installed). There also remains a culture in programming (it’s not solely PHP) of disabling peer verification rather than deal with error conditions. Interesting note: Apple has also just managed to disabled the mitigation for BEAST in Safari 7 for all platforms other than OS X 10.9 and iOS 7 – leaving Windows and earlier OS X versions potentially vulnerable.
Relying on TLS blindly has another name: being naïve. You could write a book on TLS problems.
So even with TLS implemented and working properly, users will still have trust issues. The number of times I’ve seen complaints about piping code from a download straight into the PHP interpreter (as is suggested when installing Composer) makes that abundantly clear. This approach violates the idea of knowing what you’re executing but it’s more relevantly a problem with establishing trust. We implicitly trust those who write Composer not to attack us – there’s just nothing specifically binding that trust to composer.phar and its installer.
As the recent GnuTLS vulnerability has highlighted, there is actually an app for this! In the Ubuntu/Debian ecosystem we download code. Even with TLS broken (as we know it definitely was until yesterday), we still have some faith in the packages we’ve been downloading for years because they employ an additional defence against substitution – they are signed.
File signing is a simple notion. A trusted author/maintainer signs a file with a private key that only they possess. Everyone can then verify the signature using a published public key. So long as the private key is secure, and the public key is accepted once by downloaders (with due consideration AND not replaced unless absolutely necessary), then an attacker will find it really difficult to replace that file with a tainted copy. There’s a reason for that, and it resembles the benefits of TLS pinning: you are reducing the number of trusted parties to just one – the trusted author or maintainer of the code. Even when TLS completely collapses, you can still verify the file signature to detect any tampering.
I mentioned it earlier, but Composer is NOT a package manager. The only files that Composer distributes are composer.phar and an installer script. Since it distributes nothing else, it can sign nothing else. It’s a tool that resolves dependencies and then downloads code remotely.
With that in mind there are two parts, if one were to consider file signing support for Composer:
1. Signing composer.phar and the installer; and
2. Signing everything else.
With much of the focus being on securing Composer, the first part raises a few interesting questions. The main one being how to go about establishing trust. Everything boils down to trust and it has to start somewhere. If we think hard about piping code we just downloaded through the PHP interpreter, there’s the problem of explaining how this is wrong. Is it? Let’s say the installer were signed. It sounds secure, but now we’ve just managed to shift the problem elsewhere: we have to download an initial copy of the public key. How does one sign a public key? If we trust the public key, and verify the installer using it, and an attacker has replaced both, we’ve effectively achieved…nothing. In other words, you’re left with the original problem – it’s your problem.
The flaw in Composer’s installer isn’t that it’s unsigned, it’s that it doesn’t afford the opportunity for the downloader to read it before it gets piped to PHP. It’s a documentation issue. You can go down the route of using a CA, of course, but that’s further down the rabbit hole than may be necessary.
Signing the composer.phar file is another matter. In theory, in an ideal world, you’re going to use the installer precisely once. Once you have a copy of composer.phar, you can just distribute and reuse it locally. You wouldn’t be downloading the installer hundreds of times, providing hackers with hundreds of opportunities to attack you. You’ll be copying composer.phar around and using self-update hundreds of times. Signing composer.phar therefore makes sense. That first copy can enforce signature verification on each subsequent update using the original copy of the public key. It limits the available victims of an attack to a significant degree with the bonus of unverifiable signatures creating an immediate early warning system should a compromise at the server or TLS level ever occur.
So the fuss over the installer isn’t entirely well placed. Whether it’s the installer, or a public key, that first download will be unsigned anyway. It’s the subsequent downloads that you need to watch. That will probably be my next PR looking for review. It is possible to implement an encapsulated signature verification check for phar files using openssl.
Signing Everything Else
In much the same way that one may not trust downloads from getcomposer.org, there are two other broad sources for downloaded files: packagist.org and The Planet Earth. I’m going to examine the second, but it’s worth noting that, after all else is done, at some point we will download packages.json. This demonstrates another issue with signing things. If we’re assuming that the server has no access to the private key (since an attacker would love to see that), we have to assume that any automated packages.json updates will be unsigned. Yet…this file provides all of the package metadata consumed by Composer. Let your brain cells chew on that.
Another day, another blog post…
Back to global downloads: If we don’t trust Composer files, why should we trust anyone else’s files? They are all going to lead to PHP executing downloaded code. This is the wonderful realm of package signing. You can see this in action for Debian apt (also Ubuntu…obviously). It’s a simple setup that PHP could borrow. I say PHP, because Composer is not a package manager so whether you sign anything or not is not its call though it could perhaps support the architecture so it’s actually possible.
One method is very straightforward. In a manifest file, you list all files to be downloaded with their matching checksums, i.e. the calculated hash for the file. If a file is altered, its checksum will change. Rather than sign each file (which would be ridiculous), you just sign the manifest. Upon download, the client can verify the signature of the manifest and trust its contents (or reject it if it doesn’t verify!). Once trust is established, the client can then verify that all files downloaded are a) listed in the manifest and b) have a matching checksum.
This could be implemented via git so it’s already compatible with tagged release ZIP downloads, under the assumption that tagged releases would be signed. For dev-master, well, you have to takes risks sometimes I suppose. You can’t expect something like Zend Framework to sign each and every commit. Using the master of a git repo is a risky affair.
- Apple vulnerability points to importance of secure password measures (cloudentr.com)
- New design flaw found in crypto’s TLS: Pretend to be a victim online (go.theregister.com)
- GnuTLS Certificate Verification Flaw Exposes Linux Distros, Apps to Attack (threatpost.com)
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.