There’s a new PHP remote code execution bug making the rounds, and I thought it might make for some blogging fodder, as it hits me right in my “intersection of deeply technical content + mushy culture stuff” feels. If you’re interested in learning more about this remote code execution bug, why it could effect more than nginx/PHP 7, and the cultures that lead towards these sorts of things, read on.
Historical Context
As I’ve mentioned elsewhere, there isn’t one thing you can point to that’s “PHP” — the PHP project is just a pile of C code that’s built to execute PHP programs, and then folks are free to program SAPI interfaces that allow other C based (or compatible) programs to execute PHP programs. Historically, the most famous of these was the PHP Apache Module.
Times, however, march on. While PHP continued to find massive success being embedded directly in web servers, other parts of the programming world decided to embrace the FastCGI protocol. FastCGI is a binary protocol (i.e. not HTTP, not plain-text) that lets another program (usually a web server) ask a FastCGI server to run a program for it. For reasons both technical and cultural, these FastCGI runtimes were often paired with the then light-weight nginx web server. To many people, this became a de-facto standard.
PHP and FastCGI
PHP lagged in developing a FastCGI solution, but eventually the PHP-FPM SAPI started to gain popularity — especially with folks who were used to using nginx elsewhere.
When you’re using PHP-FPM on the server, your have two daemons running.
- An nginx web server
- A PHP-FPM server
The nginx web servers will have a configuration that could look, in part, something like this
location ~ [^/]\.php(/|$) {
//...
fastcgi_split_path_info ^(.+?\.php)(./*)$
fastcgi_param PATH_INFO $fastcgi_path_info
fastcgi_pass php:9000
//...
}
This configuration basically says
If there’s a URL with a PHP file path (
http://example.com/path/to/file.php
), then ask the PHP-FPM process (on port9000
) to execute the program:path/to/file.php
Where this Falls Apart
In the C code for the PHP-FPM server/sapi, there’s this branching if statement.
if (apache_was_here) {
/* recall that PATH_INFO won't exist */
path_info = script_path_translated + ptlen;
tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0));
} else {
path_info = env_path_info ? env_path_info + pilen - slen : NULL;
tflag = (orig_path_info != path_info);
}
Specifically, this problematic line
path_info = env_path_info ? env_path_info + pilen - slen : NULL;
It looks innocuous enough — just some math, right? The problem is this isn’t normal math — it’s pointer_arithmetic — the TL;DR; on this is pointers point to memory addresses, and C lets you use math on a pointer to change its address. This is an efficient way to handle certain operations (especially on C’s “strings as arrays of bytes”), but if not done carefully you can make the pointer point somewhere it’s not allowed to, and end up with a crash.
That’s what’s happened in CVE-2019-11043. The above nginx configuration just passes along the entire URL path, so an attacker can request a carefully formed (or malformed) URL, and make the PHP-FPM process crash in such a way that they get access to the computer.
Here’s the commit that fixes this problem — you’ll notice that pointer arithmetic is still happening — but with some additional bounds checking.
Also — now that we understand the bug, it’s easy to see how some of the reporting around it is incorrect. While this bug probably won’t effect apache (due to the apache_was_here
check), it could potentially effect other web server clients of the PHP-FPM FastCGI server that are sending along the path_info without doing their own bounds checking. It could also effect people using PHP-FPM with the deprecated older 5.x line of PHPs if they’re using FastCGI. Certainly not common, but it is possible.
Why this Happens
Why these sorts of bugs happen is where we get into the big swampy morass of culture. There’s lots of vectors to consider.
First — there’s C, the language. C is a good language for carefully and performantly dealing with streams of bytes on a computer, but it’s so easy to get things wrong. Compounding that — “wrong” is often invisible, and only when your program is used in “this certain way”. C remains the a foundational component of the tools we rely on everyday, but there’s a reason 99% of projects started today don’t start with C.
Second — there’s C, the culture. The culture of C programmers drips with a machismo that if you don’t understand it, the problem’s you. This can lead to a culture where you, a less adapt C programmer, know there’s a problem with something, but don’t want to say anything unless you invoke the derision of the original program or your supposed peers. Try asking a C question on a programming forum like Stack Overflow if you’d like a taste of this.
Even when everyone’s working at the same level, there’s a tendency to prefer the dangerous techniques to safer, less performant options.
So, Not all C Programmers, etc., but this culture problem is real, and can easily lead to bugs like this slipping through.
The third, and final vector to consider, is PHP and open source culture — in particular the culture around the PHP core project itself. You get a taste of this in the initial bug report.
From your description I conclude that the issue can not happen unless FPM is misconfigured in a way that essentially makes it broken
By and large, PHP — the language — is maintained by people who aren’t working PHP programmers. It’s also, by and large, maintained by people who are volunteers. I saw some loose talk of this problem only affecting “misconfigured” nginx servers — which (as someone’s who been a working PHP programmer) seems like willful ignorance of how people use PHP in the wild.
But — someone that’s not a working PHP programmer (i.e. a core developer) might not realize how common the above web server configuration is, and might just assume that if folks are sending weird data to the FPM daemon that’s on the data sender. That might seem irresponsible (and it is), but when you’re a volunteer it’s easy to think hard things are someone else’s problem.
The PHP culture problem goes beyond the core team as well — why isn’t there a safer standard nginx configuration in use? Heck, forget safer — why isn’t there a standard one period? Why are we all relying on tutorials written as content marketing for hosting companies to make all this work?
Fortunately, the original reporter was both patient and persistent, and (to PHP core’s credit) someone eventually noticed the potential seriousness of the exploit, and a fix was available before the exploit was made public via the usual security blog PR networks. That said — as my career spiral has brought me further and further down the abstraction stack it’s somewhat — concerning? — how fragile the systems we rely on for everyday work really are.