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
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!
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?
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 just new releases or security alerts and GitHub will send you a notification in case there's one:
You can watch new releases or security alerts only (list of what you're watching)
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
Dependabot can also check your GitHub actions, see my config, an auto-created pull request to update one such action, and documentation.
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/application. There's a similar record in the all GitHub advisories database.
GitLab also offers dependency scanning but it's included only in the most expensive plan. Other Dependabot alternatives are for example Snyk or Renovate.
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
$ 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
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 (or even hourly like in my example).
Starting with Composer 2.4, you can also use
It would be cool if hosting companies would also use this database somehow to notify their customers about a required update.
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.