One of the best things to come into the PHP ecosystem in recent years has been Composer. This package manager quickly became the de-facto standard for how libraries and tools should be included into PHP applications. It was originally created around the
PSR-0 autoloading but evolved to also allow for
PSR-4 autoloading (both defined by the PHP-FIG). Composer combined these directory structure standards along with PHP's ability to define a custom autoloader to make it simple to pull in a package and put it to immediate use. The Packagist was also introduced to make it easier to locate packages that meet your needs.
Unfortunately, as anyone that's dealt with using external packages before can tell you, there's also a sort of "hidden danger" that comes along with this power and ease of install for 3rd party libraries. While the larger percentage of packages out there are benign and do a pretty decent job and what they were designed for there'll always be ones that have issues, security-related or otherwise. While you should always evaluate any package you're bringing into your application for correctness and potential security flaws it can be a lot of work, especially if the package is large.
"Surely there has to be a better way" you might be thinking...and you're right. There's a tool that can help catch the use of known vulnerable packages and prevent Composer from ever installing them. The Roave/SecurityAdvisories package hooks directly into Composer and, when a package install happens, it verifies the version against a database provided by the FriendsOfPHP group to see if the version is vulnerable.
This database of vulnerabilities] has been growing for a few years now and includes packages both large and small. Examples include:
It also includes several of the major PHP frameworks like Zend Framework, Symfony, Yii2, and CakePHP. Each of these projects includes a listing of CVE-related
yaml files containing basic information about the vulnerability including the version(s) affected, a link to more information and the related CVE entry if it exists. All of these entries are added manually through pull requests on the database repository and aren't pulled from anywhere automatically so keep that in mind when using the tool. It's only going to cover the items that have been submitted, not every potential PHP vulnerability found.
The Roave/SecurityAdvisories package is simple to install via Composer (ironic, right?) with a call using the
composer require command:
composer require --dev roave/security-advisories:dev-master
There are two things to point out about this command that may be slightly different than the normal
require you're used to. The
--dev option tells Composer to only install this package in the
require-dev section of the
composer.json configuration. This prevents it from accidentally being deployed to production while still making it available to your developers locally. The idea is that, if you can't install a vulnerable library locally then it won't make it out to production.
The second slight difference is the part following the package name, the
:dev-master addition. This tells Composer to include the latest from the project's
master branch and not bother looking for any releases. You have to be careful with pulling in packages that directly use the
master branch though. You're putting a lot of faith in the package maintainer that they'll keep that branch consistently functional and not break things. With the group from Roave it's not really much of a concern but just something to keep in mind for other projects.
Once it's installed it will check libraries you try to install and return a "conflicting package" message if an issue is found:
$ composer require symfony/symfony:2.5.2 ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - symfony/symfony v2.5.2 conflicts with roave/security-advisories[dev-master]. - symfony/symfony v2.5.2 conflicts with roave/security-advisories[dev-master]. - symfony/symfony v2.5.2 conflicts with roave/security-advisories[dev-master]. - Installation request for symfony/symfony 2.5.2 -> satisfiable by symfony/symfony[v2.5.2]. - Installation request for roave/security-advisories dev-master -> satisfiable by roave/security-advisories[dev-master]. Installation failed, reverting ./composer.json to its original content.
In this example, we tried to install version 2.5.2 of the
symfony/symfony package. The Roave advisories functionality detects that this package has issues (according to the entries in the database) and kicks back the error. It won't tell you specifically what the issue was with the version but at least it blocks the installation.
It may seem odd that the
Roave/SecurityAdvisories package spits out a conflict message rather than some other kind of message but if you dig a little deeper you'll see why. The
SecurityAdvisories package doesn't make use of code for the checking as much as it takes advantage of Composer's own features. If you look in the
composer.json for the advisories package you'll notice a long list of packages under the
conflicts section - this is the key.
When Composer tries to include a package, it looks at all of the currently installed packages and checks to see if there are any conflicts between required package versions. In this case, the
SecurityAdvisories package is manually defining the
conflicts array with a full list of the packages as pulled from the advisories database. Roave crawls this repository periodically and extracts the package names and vulnerable package versions from each of the
yaml files. This is then translated into the listing in the
conflicts section including the versions. Composer checks the package you're attempting to install and if there's a package name + version match, it kicks back the message.
For example, getting back to our attempted
Symfony install of v2.5.2, we can find the reason why it errors out. In the
conflicts dataset there's the following line:
Picking apart all of those version numbers can be a little tricky but you can see where v2.5.2 fits:
>=2.4,<2.7.38. That exact version requirement isn't defined in any of the
yaml files in the database, however. Instead, the tool that Roave uses to generate the
conflicts list does some logic to condense down the list of versions to something more manageable. Otherwise, if a package had a long list of vulnerabilities having all of them on that list would make it difficult to maintain.
Composer sees the version we're trying to install (v2.5.2) and finds a match for it in the
conflicts list and reports back the error. This approach is beneficial as even if you decide to manually modify the
composer.json to pull in that package, you wouldn't be able to update again without it erroring there too.
So far I've only talked about the Roave package for checking third-party libraries against the database when the install is attempted. There's another tool that can be used in a more isolated way to do the same kind of checking and doesn't require you modifying the
require-dev section of your application. The SensioLabs Security Checker project provides a command-line tool that scans the
composer.lock file it's given for potential issues.
To use the tool, first download the latest PHAR archive from the SensioLabs site. You can then place this file anywhere on your system and execute it via the
php command line tool:
php security-checker.phar security:check /path/to/composer.lock
For those not familiar with phars, they're a packaging format for PHP applications that allow them to be distributed as a single file rather than a package that needs to be installed as separate files.
When you run the above command if no issues are found you'll get a lovely green message stating that "No packages have known vulnerabilities." It also has a note reminding you that only the user-contributed vulnerabilities that are listed in the database will be detected.
This tool can easily be integrated into your build process to ensure that no vulnerable libraries or tools happen to slip through. If issues are found the script returns with a code of
1, otherwise it's
Having tools like the
Roave/SecurityAdvisories (and the security.sensiolabs.com frontend) on your side to help prevent potentially malicious or buggy packages from making it into your project is nice but not every package is listed there. For every one report in the database, there are thousands of lines of PHP code that haven't been evaluated for security issues in other projects. Unfortunately, there's no kind of review process or approval that Composer packages have to go through to be certified as 100% safe to use.
So, what's the alternative? Review it yourself! I know it sounds like it might be a daunting task for someone that's not as knowledgeable in the application security world but it's probably easier than you think. There's a lot of documentation out on the web around the most common vulnerability types and ways to resolve them in PHP (attacks like cross-site scripting or SQL injection). Armed with even this basic knowledge you can review most libraries for potential issues.
Here are suggestions of other things to check:
The answers to these are going to vary from package to package but the main principle to keep in mind is this: review your packages before you use them. Making use of a 3rd party package blindly is a high-risk activity for your project (and potentially your company). Take a little time to sit down and at least read through the code so you know what it's doing before it's already in use.
With over 12 years of experience in development and a focus on application security Chris is on a quest to bring his knowledge to the masses, making application security accessible to everyone. He also is an avodcate for security in the PHP community and provides application security training and consulting services.