PHP Composer security

Composer is a dependency package manager and de facto standard in PHP world.
Packagist is a PHP package repository with more than 150k different packages.

Example

Let's look composer.json of some AwesomeProject.

  • Project use few external packages such as symfony/validator, phpunit/phpunit, etc.
  • Also section autoload define two namespaces for:
    • Application source code
    • Unit-tests

Insecure composer.json

File composer.json contains few errors. Have you already guessed what mistakes?

# composer.json: insecure
{
  "name": "my/awesome_project",
  "require": {
    "php": "~7.1.7",
    "ext-mysqli": "*",
    "symfony/validator": "^4.0",
    "guzzlehttp/guzzle": "^6.3",
    "phpunit/phpunit": "^6.5",
    "squizlabs/PHP_CodeSniffer": "^3.2"
  },
  "autoload": {
    "psr-4": {
      "AwesomeProject\\": "app/",
      "AwesomeProject\\Tests\\": "tests/"
    }
  }
}

Principle of least privilege

Dependencies can be divided into two classes:

  • Required for the application (symfony/validator, guzzlehttp/guzzle)
  • Auxiliary, including packages for Unit-testing, profiling, etc (phpunit/phpunit, squizlabs/PHP_CodeSniffer)

When you deploy a production environment, you only need the code necessary to run the application. Tests, code-style fixers and the rest of the helper code runs on a different environment, for example, on development machines and in automatic assembly on Staging.

Obviously, composer.json violates the principle of minimum privileges, since auxiliary packages, should not have access to the production environment. Each dependency is code, and any code can contain errors, including security.

For this reason, Composer provides a special flag composer install --no-dev for deployment on the production environment, in which

  • No packages will be installed from the require-dev section
  • No autoloader will be generated for namespaces from the autoload-dev section

1. require-dev section

Packages specified in the require-dev section are not used by the application on production environment. These packages are auxiliary and need to be developed on the development environment or testers.

Examples of such packages are:

  • phpunit/phpunit - Unit-testing library
  • codeception/codeception - Acceptance-testing library
  • squizlabs/PHP_CodeSniffer - code-style fixer
  • fzaninotto/faker - test data generator
  • mockery/mockery - Mock/Stub framework for Unit-testing
# composer.json: require-dev section
{
  "name": "my/awesome_project",
  "require": {
    "php": "~7.1.7",
    "ext-mysqli": "*",
    "symfony/validator": "^4.0",
    "guzzlehttp/guzzle": "^6.3"
  },
  "require-dev": {
    "phpunit/phpunit": "^6.5",
    "squizlabs/PHP_CodeSniffer": "^3.2"
  },
  "autoload": {
    "psr-4": {
      "AwesomeProject\\": "app/",
      "AwesomeProject\\Tests\\": "tests/"
    }
  }
}

2. autoload-dev section

In addition to the main code, the project can contain an auxiliary code, for example

  • Unit-tests
  • Code bottlenecks profiling
  • Code for build a new release version

All this code is not executed on the production environment, hence the declaration of namespace should be in the autoload-dev section.

# composer.json: autoload-dev section
{
  "name": "my/awesome_project",
  "require": {
    "php": "~7.1.7",
    "ext-mysqli": "*",
    "symfony/validator": "^4.0",
    "guzzlehttp/guzzle": "^6.3"
  },
  "require-dev": {
    "phpunit/phpunit": "^6.5",
    "squizlabs/PHP_CodeSniffer": "^3.2"
  },
  "autoload": {
    "psr-4": {
      "AwesomeProject\\": "app/",
    }
  },
  "autoload-dev": {
    "psr-4": {
      "AwesomeProject\\Tests\\": "tests/"
    }
  }
}

3. Automatic check for vulnerabilities

The SensioLabs company, known as the developer of Symfony framework, supports the known PHP packages vulnerabilities database (CVE). This allows to check the composer.lock file for vulnerabilities.

In order to automatically run the test each time the composer install or composer update command is executed, it is enough to put the API request in the post-autoload-dump section.

SensioLabs Security checker stats

# composer.json: auto security checker
{
  "name": "my/awesome_project",
  "require": {
    "php": "~7.1.7",
    "ext-mysqli": "*",
    "symfony/validator": "^4.0",
    "guzzlehttp/guzzle": "^6.3"
  },
  "require-dev": {
    "phpunit/phpunit": "^6.5",
    "squizlabs/PHP_CodeSniffer": "^3.2"
  },
  "autoload": {
    "psr-4": {
      "AwesomeProject\\": "app/",
    }
  },
  "autoload-dev": {
    "psr-4": {
      "AwesomeProject\\Tests\\": "tests/"
    }
  },
  "scripts": {
    "post-autoload-dump": "curl -s -H \"Accept: text/plain\" https://security.sensiolabs.org/check_lock -F lock=@composer.lock"
  }
}

Summary

  1. Use composer install --no-dev for production deployment
  2. Unused application packages on the production environment (profilers, codeStylesFixers, etc) must be in the section require-dev
  3. Unused application namespace's on the production environment (Unit-tests) must be in the section autoload-dev
  4. It is recommended to add an automatic check for known vulnerabilities in packages by the SensioLabs database.