There’s been a lot of chatter about PHP being insecure, but as Luke Stephens points out in his article, "People who say 'PHP is insecure' are uninformed", PHP is not that bad anymore. He breaks down why this belief is outdated and misinformed. One thing this article does not cover is something a lot of people don't realize: Programming languages change over time. PHP from 15 years ago is different from today's PHP.
If you are old-school, you probably remember Stefan Esser (i0n1c) and "The Month of PHP Bugs". Basically, Stefan spent March 2007 dropping vulnerabilities in PHP—not PHP applications, but PHP itself. This "Month of PHP Bugs" was started to increase awareness about PHP's lack of security. At the time, the PHP Hardened team was working on providing hardened versions of PHP (the "suhosin" patch). You can still find details about those bugs on web.archive.org: "The Month of PHP Bugs".
Around this era, PHP applications were extremely common and had a very poor security track record.
All of this didn't help PHP's reputation. But it was 17 years ago, and a lot has changed since then. One main thing is that PHP has reduced the number of surprising behaviors for developers. Let's dive in!
The PHP language has evolved significantly over the years. Many tricks to find vulnerabilities in PHP applications or exploit vulnerabilities in PHP have been rendered obsolete with new versions. Let's look at some of the significant changes!
PHP used to rely on C functions to access files. C functions are notoriously bad at handling NULL bytes (\x00
or URL-encoded %00
). If you pass the string /etc/passwd\x00hacktheplanet
to read a file, C functions will stop at the NULL byte
and read the file /etc/passwd
. This behaviour created a lot of vulnerabilities or made a lot of weaknesses exploitable. PHP moved away from this with PHP 5.3.4 by fixing CVE-2006-7243.
PCRE_EVAL
Another surprising behavior in PHP was available when you had control over a regular expression. The infamous PCRE_EVAL
or /e
could be used to have the result evaluated as PHP code, allowing for quick remote code execution. For example, in the snippet below:
echo preg_replace("/test/e","get_current_user()", "test");
will end up replacing test
with get_current_user()
and execute get_current_user()
as PHP code... An real-life example of the danger of this can be found by analyzing CVE-2005-2086: a remote code execution impacting PHPBB. This behavior was removed in PHP 7.0.0 (released in December 2015)
Loose comparisons have also been a big issue in PHP. Why does "php" == 0
or why does "1`uname`" == 1
? This unexpected behavior was changed in PHP 8.0. Look for the little stars in the image below and the notes below the table:
While we're at it, why is declare(strict_types=1);
so rarely used by PHP applications?
PHP 8.0 also impacted the ability to gain remote code execution (RCE) by leveraging assert()
. The contrived example below illustrates the issue:
echo assert(trim("'".$_GET['a']."'"));
and will lead to code execution.
Another unexpected behavior of PHP was the fact that, by default, assert()
wouldn't stop the execution of the code. Admittedly, you could change this behavior, but as good application security engineers know: "defaults matter". With PHP 8.0 (August 2023), the behavior has changed, and a failing assert()
will stop the execution of the code by default! Before PHP 8.0, the following code:
echo assert(true == false);
echo "HACK";
will echo HACK
in the default PHP configuration
hash_hmac()
Speaking of warnings, the following code illustrates a good trick to bypass a signature check - passing an array instead of a string:
hash_hmac('sha256', array(), "HACKTHEPLANETWITHPENTESTERLAB");
This code returned NULL and raised a warning until PHP 8.0; since then, it has been raising a TypeError
.
Another trick to find quick bugs in PHP applications was to look for values echoed in a page in JavaScript using single quotes. This used to work because PHP didn't escape single quotes by default. Again, in application security, defaults matter! This behavior was changed with PHP 8.1.
Before PHP 8.1:
echo htmlentities("\"'<>"); //returns "'<>
After PHP 8.1:
echo htmlentities("\"'<>"); //returns "'<>
Well, there are still some surprises and interesting behaviors that were recently fixed in PHP. For example, check out this bug in password_verify()
(The first comment is worth reading just to learn how not to do application security).
PHP 8.1 also introduced new features that may surprise developers and likely bring a few bugs: $_FILES[...]["full_name"]
. It's worth keeping an eye out for this in your next reviews.
PHP has come a long way from its insecure past. While it has made significant strides in improving security and reducing unexpected behaviors, it's crucial for developers to stay informed about new features and potential security issues. Keeping up-to-date with the latest changes ensures that PHP applications remain secure and robust.