January 25, 2023

When a security vulnerability is discovered in one of the PHP libraries you use, there are several options how you can learn about the bug before it's too late. I've written about PHP Security Advisories Database in one of my previous posts and how you can use it with Roave Security Advisories and a few other ways. However all of them require an extra package or a tool.

The roave/security-advisories package has a long list of vulnerable libraries including versions, and trying to install one of those will fail with a warning. Besides the package I also use a GitHub Action called The PHP Security Checker, which in fact is the Symfony CLI security check packaged as an action. I've written about all these in my previous article, including examples.

But if you don't want to use anything extra and would like to utilize something you already use, then I have some good news for you. Starting with version 2.4 (released in summer 2022), Composer can query the database directly. It does that automatically after every composer require and you can also check installed packages manually with composer audit. Composer uses its own API, see for example a list of vulnerable guzzlehttp/guzzle versions.

Packages are also checked after running composer update (in both cases the audit can be skipped with --no-audit) but not by default after composer install – the reason may be that it's often done automatically and there's no one to see the result anyway, or possibly the fact that it's done so often, for example when running tests, that it may lead to just too much API queries. But if you want, you can still run an audit after installation is complete with --audit.

After composer require

Let's preview how it looks like. Let's say I'd like to install a package version with a known vulnerability, like for example Guzzle 7.4.4 – Composer will install it but it will complain and alert me, check the last two lines:

$ composer require guzzlehttp/guzzle:7.4.4
./composer.json has been created
Running composer update guzzlehttp/guzzle
Loading composer repositories with package information
Updating dependencies
Lock file operations: 8 installs, 0 updates, 0 removals
  - Locking guzzlehttp/guzzle (7.4.4)
  - Locking guzzlehttp/promises (1.5.2)
  - Locking guzzlehttp/psr7 (2.4.3)
  - Locking psr/http-client (1.0.1)
  - Locking psr/http-factory (1.0.1)
  - Locking psr/http-message (1.0.1)
  - Locking ralouphie/getallheaders (3.0.3)
  - Locking symfony/deprecation-contracts (v3.2.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 8 installs, 0 updates, 0 removals
  - Installing symfony/deprecation-contracts (v3.2.0): Extracting archive
  - Installing psr/http-message (1.0.1): Extracting archive
  - Installing psr/http-client (1.0.1): Extracting archive
  - Installing ralouphie/getallheaders (3.0.3): Extracting archive
  - Installing psr/http-factory (1.0.1): Extracting archive
  - Installing guzzlehttp/psr7 (2.4.3): Extracting archive
  - Installing guzzlehttp/promises (1.5.2): Extracting archive
  - Installing guzzlehttp/guzzle (7.4.4): Extracting archive
2 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
4 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Found 2 security vulnerability advisories affecting 1 package.
Run composer audit for a full list of advisories.

It's more prominent with colors in your terminal:

Poslední 2 řádky z výstupu composer require

You can change the format, see below, but still, you could easily miss it. Especially if you'd run composer require with --quiet meaning “do not output any message”.

composer audit

Maybe the very last line is more important then, it says to run composer audit:

$ composer audit
Found 2 security vulnerability advisories affecting 1 package:
+-------------------+----------------------------------------------------------------------------------+
| Package           | guzzlehttp/guzzle                                                                |
| CVE               | CVE-2022-31091                                                                   |
| Title             | Change in port should be considered a change in origin                           |
| URL               | https://github.com/guzzle/guzzle/security/advisories/GHSA-q559-8m2m-g699         |
| Affected versions | >=7,<7.4.5|>=4,<6.5.8                                                            |
| Reported at       | 2022-06-20T22:24:00+00:00                                                        |
+-------------------+----------------------------------------------------------------------------------+
+-------------------+----------------------------------------------------------------------------------+
| Package           | guzzlehttp/guzzle                                                                |
| CVE               | CVE-2022-31090                                                                   |
| Title             | CURLOPT_HTTPAUTH option not cleared on change of origin                          |
| URL               | https://github.com/guzzle/guzzle/security/advisories/GHSA-25mq-v84q-4j7r         |
| Affected versions | >=7,<7.4.5|>=4,<6.5.8                                                            |
| Reported at       | 2022-06-20T22:24:00+00:00                                                        |
+-------------------+----------------------------------------------------------------------------------+

That's much better. Here you see the vulnerable package name and even the vulnerability. Even the return value is not zero which means something went wrong, so this could be used in scripts, too:

$ echo $?
1

This is how it will look like when trying to install the newest version without known security vulnerabilities:

$ composer require guzzlehttp/guzzle
[...]
No security vulnerability advisories found
Using version ^7.5 for guzzlehttp/guzzle
[...]

And this is the composer audit output then, including the command's return code which is now zero, meaning everything's al­right:

$ composer audit
No security vulnerability advisories found
$ echo $?
0

For composer audit to work properly the packages must be installed by default. But if you use --locked (composer audit --locked) then the audit is based just on the composer.lock file and there's no need to install the packages beforehand.

Use --no-dev if, for whatever reason, you'd like to disable auditing packages listed in require-dev. I'd personally check everything though.

As a GitHub Action

Thanks to the non-zero return value when a package with a known vulnerability is installed, composer audit can easily be used in GitHub Actions for example. And that's exactly how I'm using it as well:

composer-audit:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v3
    - run: composer audit

I run this check every two hours so that I know about a possible problem and the need to update in time. For a more important application than my website, I'd be happy to run it more often, like every hour.

The result of running a package check every 2 hours on GitHub Actions

Running a check on GitHub Actions

Output format

Composer can output the vulnerability information in several formats:

  • table: a table, see above, default for composer audit
  • plain: a textual output, no table
  • json: guess
  • summary: just a short info whether something has been found, default for composer require, composer updatecomposer install

You can change the format with:

  • --audit-format=FORMAT for composer require, composer updatecomposer install
  • --format=FORMAT for composer audit

One more thing: you can update Composer by running composer self-update.

Updates

January 27, 2023 With --locked, the audit is based on composer.lock instead of the installed packages

Michal Špaček

Michal Špaček

I build web applications and I'm into web application security. I like to speak about secure development. My mission is to teach web developers how to build secure and fast web applications and why.

Public trainings

Come to my public trainings, everybody's welcome: