November 9, 2020

At the end of August, a critical security bug was discovered and immediately fixed in one of the popular PHP frameworks, Nette. Although the author of the framework, David Grudl, did everything possible, some did not learn about the bug in time and did not update their sites and web apps. Let me tell you a few tips not only for PHP, that will help you to know about similar problems as soon as possible.

The bug, discovered by Cyku Hong from Taiwan, can, under certain circumstances when using a specially crafted URL, lead to remote code execution (RCE). Such bugs are always fun, except when you run the site.

David Grudl, the creator of the framework, has done everything correctly to address the issue: due to the high severity of the bug, new versions were released “silently” first, including updates to versions that are no longer supported for 6 years already, an announcement was sent to supporters and only after more than a month, information was published in a blog post, on a forum and in the Common Vulnerabilities and Exposures list under CVE-2020-15227. (David has also asked me if he wasn't sure about something 🤓)

Patched versions as listed in the blog post, some very old and otherwise not supported

On a massive scale and without Composer

A week after the information about the bug has been published, not only testing tools and PoC (Proof-of-Concept) exploits have started appearing but also developers that previously had no idea that such bug exists and has been already fixed. After noticing this, I realized that maybe, a simple script is needed that would find vulnerable files on the server's drive and possibly fix them by replacing one line of code with another.

The intended users of the script would be companies offering shared hosting: they would notify their particular customers, or even resolve the issue for them. I've created two shell scripts (they both run in Bash in Linux & FreeBSD), David has followed up with a pure PHP implementation. And just like that, Lukáš V. has even donated $10 for those scripts, thanks!

Here's Active24 tweeting (in Czech) they've found and patched 2500+ potentially vulnerable sites using my script, similar to what Blueboard, WEDOS and some others have said

This alone might give you some idea what to do when a critical security bug is found in your framework, package, or library. Equally important is to notify the users of your code about the issue and the need to update their sites. But how can they learn about the bug as soon as possible?

Support and follow the developers

Here are some options for you to learn that you need to update the library or a framework you use, like in the Nette Framework case, and this is probably the most important one.

Support the developers of the tools you use. Not only for a good feeling but the developers will also have a way and an address to email you to let you know you should update, and the money you donate. Win-win situation. When we've started with the Nette bug, yes, I do support Nette personally and I'm sort of easy to spot in the list of backers. You'll find Scott Helme there as well because we also use Nette on Report URI, which I happen to work on with him. The author of the framework sent an update advisory to the supporters already on August 25, just a few hours after the fixed versions became available, and more than a month before he went public with the bug.

Follow the developers on Twitter, follow their blogs & forums (see the Nette blog post and a forum post). If the developers are using GitHub, you can watch new releases and GitHub will send you a notification in case there's one:

You can watch new releases only (list of what you're watching)

GitHub Dependabot

If you're using GitHub to host your repositories, and Composer to manage your dependencies, I suggest you learn about Dependabot – a robot that helps you to keep your dependencies up-to-date. Dependabot knows about PHP and handful of other languages and package managers. You can set it up in your repository, in Settings > Security & analysis:

Explore the Security > Dependabot alerts section as well, it lists all open and closed alerts created by Dependabot:

I've put this alert away by clicking Dismiss, the code is not running anywhere

The section also contains links to automatically created pull requests, see the example. This abandoned project is not deployed anywhere, so nette/application doesn't really need to be updated here. I want to leave the pull request open and not merged so you can see how a Dependabot-created pull request would look like in your repository.

Automatically created pull request with a dependency update

If you'd ever need to get a CVE number for your own code to get the information about a vulnerability out, you'll find the Security Advisories section useful. The number of the Nette bug, CVE-2020-15227, is also listed in all published advisories for nette/applica­tion. There's a similar record in the all GitHub advisories database.

Roave Security Advisories

The next tool that can help you discover outdated PHP libraries with security issues is Roave Security Advisories package. Let's try to install it, we'll use my old and unused project again. First, let's install the dependencies listed in composer.lock, whereas it would more apropriate to use composer update but that would install the already patched version which we don't want right now:

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
[...]
  - Installing nette/application (v2.3.7): Downloading (100%)
[...]

Version 2.3.7 contains the security bug (up to and including 2.3.13 in the 2.3 line), so installing the Roave Security Advisories package with

composer require --dev roave/security-advisories:dev-latest

fails because of a conflict with version 2.3.7 of nette/application:

$ composer require --dev roave/security-advisories:dev-latest
./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
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - roave/security-advisories dev-latest conflicts with nette/application[v2.3.7].
    - Installation request for roave/security-advisories dev-latest -> satisfiable by roave/security-advisories[dev-latest].
    - Installation request for nette/application (locked at v2.3.7, required as ~2.3.1) -> satisfiable by nette/application[v2.3.7].


Installation failed, reverting ./composer.json to its original content.

We'd need to first update nette/application to 2.3.14 or newer and then it'd possible to install roave/security-advisories:dev-latest. Since then, it would block installing any package with a known security vulnerability, while doing composer require or composer update.

PHP Security Advisories Database

Roave Security Advisories contains no code, the know-how lies in the conflict section in its composer.json, which presents a list of all packages and versions with a known security bug. The source of those is a database called PHP Security Advisories Database (see Contributing) which can be used in a few more ways. It can also be used as a GitHub Action (see my config) to check dependencies on each repository push for example.

It would be cool if hosting companies would also use this database somehow to notify their customers about a required update.

disable_functions

The only real defense against bugs like RCE is probably timely updates. Of course, your firewall can also block some URL patterns but that's more a reactive mitigation. You can at least lower the impact of the attack – you know, it's already quite bad when the attacker can run some PHP code in your app but it's much badder when they can run some external program from your PHP. I'd strongly suggest to disable these PHP function calls that can execute external programs:

  • exec – execute an external program
  • passthru – execute an external program and display raw output
  • proc_open – execute a command and open file pointers for input/output
  • shell_exec – execute command via shell and return the complete output as a string
  • system – execute an external program and display the output
  • pcntl_exec – executes specified program in current process space
  • popen – opens process file pointer

You can disable them only in php.ini (even for a specified server or path only) with disable_functions or in a FPM pool config with php_admin_value[disable_functions], see my example.

To check if you're not calling those functions in your own PHP code, use a static analyzer like PHPStan with my custom rules to flag disallowed function & method calls. Just include bundled disallowed-execution-calls.neon in your PHPStan config.

Also don't forget to follow me on Twitter, I tweet about critical problems like the one found in Nette too.

One more thing: if you use Nette and haven't updated yet it's almost for sure that someone has uploaded some remote shells or backdoors into your site or web app by now. You should initiate recovery and cleanup process, maybe even look for some data leaks. And if you've updated already, you can have fun with Easter eggs on my site.

Updates

November 20, 2020 GitHub has moved new Releases notifications setup to Custom category

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:

HTTPS for developers and admins
(December 8–9, 2020 )

PHP application security
(December 14–17, 2020 )