The PHP-CGI Query String Bug, Explained
Earlier this month the PHP team disclosed a bug in php-cgi that, in the right setup, lets a remote attacker dump your source code or run arbitrary code. It’s CVE-2012-1823, and the uncomfortable part is that the release announcement says it had gone unnoticed for at least eight years. The mechanism is a clean example of two reasonable behaviors combining into something dangerous, so it’s worth understanding rather than just patching and moving on.
Where the query string becomes an argument
The root of it is in the CGI spec. RFC 3875 section 4.4 describes the script command line: for an “indexed” query, meaning a GET or HEAD request whose query string contains no unencoded = character, the server should split the query string into words and add them to the script’s command line argument list. This is an ancient feature from the days when a query like ?apache+server was meant to become search terms passed as argv to a script.
Now line that up with php-cgi, which is a normal command line program that accepts options. php-cgi -s dumps a syntax highlighted copy of the source. php-cgi -d sets an arbitrary php.ini directive. So a request whose query string had no = got its contents handed to php-cgi as command line options. Ask for /index.php?-s and you get the page’s source back. From -d it gets much worse, because you can set directives like auto_prepend_file and reach real code execution.
The reason a normal request is safe is exactly the RFC’s condition. A real query string like ?id=42&sort=name contains = characters, so it isn’t treated as an indexed query and never reaches the argument list. It’s the argument-only strings starting with a dash that slip through.
Who was actually exposed
This is the part worth being precise about, because it’s narrower than the panic suggested. The announcement is explicit that mod_php and php-fpm are not vulnerable, and neither is nginx with php-fpm. The problem is specific to running PHP as a CGI binary, in particular Apache with mod_cgi calling php-cgi. The team also noted that straight shebang-style CGI does not appear to be vulnerable.
If you weren’t sure which bucket you were in, the announcement gave a one-request test. Add ?-s to the end of any URL on your site. If you get the page’s source back, you’re vulnerable. If the page renders normally, you’re not. That’s a clean check, and I’d rather run it than guess from my config.
The fix took more than one try
PHP 5.3.12 and 5.4.2 shipped on May 3 as the fix. Then, on May 6, the team posted a follow-up saying those releases did not fix all variations of the issue. NVD’s entry for the follow-up, CVE-2012-2311, states plainly that it exists because of an incomplete fix for CVE-2012-1823, this time for query strings containing a %3D sequence but still no literal =. PHP 5.4.3 and 5.3.13, on May 8, completed the fix, and 5.4.3 also patched an unrelated buffer overflow in apache_request_headers(). So the versions you actually want are 5.4.3 or 5.3.13, not the first patch.
If upgrading a legacy CGI box isn’t quick, the team suggested blocking the dangerous requests at the web server. Their mod_rewrite rule rejects a query string that starts with a dash (encoded or not) and contains no =:
RewriteCond %{QUERY_STRING} ^(%2d|-)[^=]+$ [NC]
RewriteRule ^(.*) $1? [L]
That should not break normal sites, though if you have legitimate URLs shaped like ?-something you’ll want to adjust the pattern.
The wider lesson
The cleanest long term fix isn’t a rewrite rule, it’s not running PHP as a CGI binary. mod_php and php-fpm both sidestep this entirely, and php-fpm is the better answer under nginx or behind Apache’s mod_proxy_fcgi anyway. But the bug is a good reminder of how these things happen: the CGI argument feature was documented and correct, php-cgi’s options were documented and correct, and nobody owned the seam where a client controlled string turned into program arguments. When you inherit an old CGI setup, that seam is exactly where I’d look first.