medium.com 11 min read Original Source

Think Php2Shell Was Bad? PHP Is About to Face an Absolute Bloodbath

I’ve been writing PHP since 1999, before everyone migrated from Rasmus Lerdorf’s original PHP/FI 2.0 to PHP 3.0. PHP3 was a complete rewrite of the original. It even came with a new *.php3 file extension instead of the commonly used *.phtml or *.php extension. PHP had design flaws like register_globals and magic_quotes_gpc between 1998 and 2012, alongside severe security issues like CVE-2006–2563 in PHP 4.4.2 to 5.1.4 from 2006. Even back then, it had a bad reputation and was not well regarded. I still remember the endless debates with Linux admins about PHP.

Much of PHP’s security issues related to memory management in C (e.g. CVE-2006–2563), but that by far wasn’t the sole or even main cause. PHP’s design philosophy was largely “make web dev as frictionless as possible for beginners“. PHP implemented language features that left experienced programmers scratching their head. Even if PHP was rewritten in Go or Rust today, these issues would largely remain. I’d even go so far as to say that it’s not even the language itself that is at fault.

Why people even use PHP

Today, there’s zero reason for an experienced polyglot programmer to pick PHP over any other language like Rust, Go or C++. PHP flourished during the Dot-com bubble, when everyone needed to build websites fast and cheap. In 2000, First content management systems like PHP-Nuke gained popularity and phpBB bulletin boards were popping up everywhere, for every topic imaginable. Back in the late 90s and early 2000s, PHP was what Node.js was in the early 2020s; The go-to solution for building web apps.

With this surge in demand for all kinds of Internet forums with phpBB, blogs with WordPress, web shops in osCommerce, and tons of custom PHP code, a job titled “Web Developer” appeared on the horizon. It was the next logical evolution of the “webmaster“, in a time in which companies tried to figure out where to put “the Internet people” in their organisation.

The new “Web Developers” were rarely seasoned programmers who found their joy in Internet software, but often juniors who got into the web early, or digital designers who wanted a share of the Internet pie. PHP wasn’t just the new framework for “the web”. It was also an entry level programming language. And entry level programmers rushed to fill the never ending demand for “websites”, and became the much needed “web developers”.

In 2006, PHP was everywhere. I wrote endless amounts of PHP code, and I had to maintain PHP code written by others. Very often written by people who had absolutely no business in writing software. They copy-pasted free PHP code snippets from other sites, or worse bought code snippets from dodgy sites. PHP code snippets were a real and legit business back then. So much PHP was written, that even today about 70% of all websites use it.

Many PHP programmers went on to other languages, but not everyone became a polyglot programmer. A good amount of early adopters, and beginners alike, became die hard PHP fans and believers. Especially in Germany and France as Europe has one of the strongest PHP communities across the entire globe. Many popular PHP apps are home to Europe, including Drupal from Belgium, Typo3 from Denmark (now mainly German/Swiss), Symfony from France, or Shopware from Germany.

There’s just too much bad PHP code

I’ve written too much PHP code over the decades, and I’ve given up on it for really good reasons. Unfortunately, that never stopped people from shoving PHP down my throat, forcing me to write and maintain it against my will. I don’t reject PHP because of some awkward trauma, but because of its continuous design flaws and really poor runtime performance.

It’s hundreds of thousands of lines of code that I have to review every year, in every programming language imaginable. JavaScript code is getting very close, but PHP code remains to be some of the worst code I have to read. Don’t get me wrong, there are people who write really good PHP code, and they have my highest respect for it. But the majority? The majority isn’t very desirable to read for an experienced programmer. That has nothing to do with the language, but with the quality of code that is produced in it.

Call-time pass-by-reference — and other early red flags

PHP had the paradigm to be a paradise for developers, not so much for administrators. It wanted to be loved by programmers, and tried to make everyone’s wish come true. This resulted in often absurd implementations like “Call-time pass-by-reference”. That feature allowed to decide, during runtime, whether a parameter would be passed to a function by value or by reference. A recipe for disaster that was deprecated in PHP 5.3.

<button data-code="” data-toggle-ms=”1200″ aria-label=”Copy code” data-nosnippet=”” class=”code-copy-btn absolute right-3 top-3 size-8 p-1.5 flex items-center justify-center bg-black/50 text-white rounded-md transition-colors duration-200 cursor-pointer hover:bg-black/70″>

<?php
// Function expects parameter BY VALUE (no '&' in definition)
function increment($val) {
    $val++;
}

$count = 10;

// Call-time pass-by-reference:
// The '&' forces $count to be passed as a reference
increment(&$count);

echo $count; // Outputs 11
?>
<?php
// Function expects parameter BY VALUE (no '&' in definition)
function increment($val) {
    $val++;
}

$count = 10;

// Call-time pass-by-reference:
// The '&' forces $count to be passed as a reference
increment(&$count);

echo $count; // Outputs 11
?>

Features like “calltimebyref” highlighted that PHP would have no problem sacrificing security for obscure features demanded by, often very few, developers. Many other awkward features were added, including ASP Tags (<% %>), Script Tags (<script language="php">), or eval() hidden in preg_replace(). The list of obscurities was almost endless in PHP4 and continued into PHP5.

// Whatever matched inside captured groups would be evaluated as PHP code
$text = preg_replace('/([a-z]+)/e', "strtoupper('\1')", $input);
// Whatever matched inside captured groups would be evaluated as PHP code
$text = preg_replace('/([a-z]+)/e', "strtoupper('\1')", $input);

Back in my days, the experienced boomer programmers were baffled by the insanity that came with every new PHP release. 20 years ago, writing PHP was frowned upon by the elderly. Even if you wrote commercial PHP modules and built complex Oracle integrations.

Awkwardness continued well into PHP8

With PHP8, people hoped the weirdness was over and PHP would finally become a language for adults. Perl-style alphanumeric increment was deprecated with PHP 8.3, and dynamic properties on arbitrary objects were gone with PHP 8.2, although only throwing a deprecation warning. PHP had to find the happy balance between their existing community that demanded PHP’s style and paradigms, and the industry demand for it to become a mature and trustworthy programming language.

nmae = "Alice"; // Typo! Silently creates a new runtime property” data-toggle-ms=”1200″ aria-label=”Copy code” data-nosnippet=”” class=”code-copy-btn absolute right-3 top-3 size-8 p-1.5 flex items-center justify-center bg-black/50 text-white rounded-md transition-colors duration-200 cursor-pointer hover:bg-black/70″>

$val = 'AZ';
$val++;
echo $val; // Outputs "BA"
class User {
    public string $name;
}

$user = new User();
$user->nmae = "Alice"; // Typo! Silently creates a new runtime property
$val = 'AZ';
$val++;
echo $val; // Outputs "BA"
class User {
    public string $name;
}

$user = new User();
$user->nmae = "Alice"; // Typo! Silently creates a new runtime property

PHP’s awkward features didn’t just lead to bad code from inexperienced programmers, they also led to applications that “just worked” by accident. Some apps carried accidental typos throughout the entire codebase, fearing that a fix might break the entire app. There’s still legacy codebases today that operate on PHP versions prior to 7. Some of them have reached a state of unbelievable unmaintainability, one of COBOLesque dimensions.

LAMP, a child of the 3-tier-web-app era

LAMP stands for Linux, Apache, MySQL, and PHP. It was the default “tech stack” for web apps in the early 2000s, and often is to this date. LAMP, and the early 3-tier-web-app, was the predecessor to today’s event driven microservices in distributed high availability architectures.

PHP doesn’t force the developer into a legacy architecture. It doesn’t enforce an RDBMS or on-demand server-side rendering. However, it is still the standard approach for many popular PHP applications, including WordPress. The AWS reference architecture for WordPress outlines the complexities of running WordPress in a high availability environment.

I’ve deployed and operated this reference architecture dozens of times, and still operate many of them today. When you setup PHP on an auto scaling EC2 fleet with EFS, you will quickly realize how much you need to bend PHP applications into that frame. The absence of “cronjobs” and the ephemeral nature of the compute unit (EC2 instance) are environmental factors that many PHP developers, and thus apps, don’t consider in their implementations. This is especially true for WordPress plugins.

High availability setups for PHP applications require a lot of auxiliary applications to run containerized scheduled PHP scripts, cache long running scripts, and integrate key-value stores like DynamoDB around the PHP application itself. I ended up writing a lot of Go code that assists PHP in its execution. Often purely driven by cost optimization requirements. After all, interpreted PHP isn’t the most cost efficient to operate.

The PHP community doesn’t like to hear it, but PHP is the poster child of a long gone era of web development. Most of the implementation strategies used by PHP developers date back to the early 2000s. I rarely see serverless microservice concepts in today’s PHP community. I do see them, but it is extremely rare. A lot of today’s PHP implementation strategies are still ye olde LAMP way. Very often with a Redis cluster smashed on top, because that’s what you need today, isn’t it?

The Bloodbath has just begun

PHP2Shell with CVE-2026–109618 and CVE-2026–109619 are only just the beginning of uncovering decade old issues in the runtime environment, and within many popular PHP applications. Memory management was always PHP’s achilles heel, like it is for many weakly typed programming language. PHP2Shell is a severe vulnerability in the runtime environment itself. It affects every PHP codebase out there, but it’s not the only problem PHP is faced with today, and may not even be the biggest.

Embedded content

A look into the Apache2 Handler-SAPI integration gives you a glimpse into how PHP’s code is structured. Like with many Open Source projects, you’ll hardly find inline comments with explanations. While that’s common in many expensive professional codebases, it’s often quite uncommon in Open Source. That’s nothing to worry about as most of it is indeed self explanatory. PHP’s source code perfectly shows the often nested and intertwined memory management across the entire application.

r;
if (walk_to_top) {
while(r->prev) {
r = r->prev;
}
}

env_val = (char*) apr_table_get(r->subprocess_env, variable);

if (env_val != NULL) {
RETURN_STRING(env_val);
}

RETURN_FALSE;
}
/* }}} */” data-toggle-ms=”1200″ aria-label=”Copy code” data-nosnippet=”” class=”code-copy-btn absolute right-3 top-3 size-8 p-1.5 flex items-center justify-center bg-black/50 text-white rounded-md transition-colors duration-200 cursor-pointer hover:bg-black/70″>

/* {{{ Get an Apache subprocess_env variable */
/*
 * XXX: shouldn't this be the parent not the 'prev'
 */
PHP_FUNCTION(apache_getenv)
{
 php_struct *ctx;
 char *variable;
 size_t variable_len;
 bool walk_to_top = 0;
 char *env_val=NULL;
 request_rec *r;

 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b",
    &variable, &variable_len, &walk_to_top) == FAILURE) {
  RETURN_THROWS();
 }

 ctx = SG(server_context);

 r = ctx->r;
 if (walk_to_top) {
  while(r->prev) {
   r = r->prev;
  }
 }

 env_val = (char*) apr_table_get(r->subprocess_env, variable);

 if (env_val != NULL) {
  RETURN_STRING(env_val);
 }

 RETURN_FALSE;
}
/* }}} */
/* {{{ Get an Apache subprocess_env variable */
/*
 * XXX: shouldn't this be the parent not the 'prev'
 */
PHP_FUNCTION(apache_getenv)
{
 php_struct *ctx;
 char *variable;
 size_t variable_len;
 bool walk_to_top = 0;
 char *env_val=NULL;
 request_rec *r;

 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b",
    &variable, &variable_len, &walk_to_top) == FAILURE) {
  RETURN_THROWS();
 }

 ctx = SG(server_context);

 r = ctx->r;
 if (walk_to_top) {
  while(r->prev) {
   r = r->prev;
  }
 }

 env_val = (char*) apr_table_get(r->subprocess_env, variable);

 if (env_val != NULL) {
  RETURN_STRING(env_val);
 }

 RETURN_FALSE;
}
/* }}} */

Experienced C/C++ programmers won’t have an issue with the way PHP works. Security challenges can arise when a high level language like PHP obfuscates the underyling memory management, and a programmer unwillingly executes procedures who’s combination can result in memory issues or other unwanted execution behavior. This isn’t a vulnerability, but a general security weakness in interpreted languages. Node.js and Ruby share the same weakness with PHP.

The most common attack vector against PHP applications was and still is a denial of service attack against the database. As mentioned earlier, PHP apps often excessively use RDBMs like Postgres or MariaDB, even when a key/value-store may be the better solution. Additionally, ORMs like Doctrine or Cyclone ORM are still commonly found in PHP apps. While SQL Injection are now properly addressed in most PHP applications, the performance and memory issues with ORMs and RDBMs aren’t. We will likely see a lot more application specific vulnerabilities targeting the database implementations of popular PHP applications.

/* {{{ _mysqlnd_pememdup */
static char * _mysqlnd_pememdup(const char * const ptr,
              size_t length, bool persistent MYSQLND_MEM_D)
{
 char * ret;
 bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
 TRACE_ALLOC_ENTER(mysqlnd_pememdup_name);

#if PHP_DEBUG
 {
  const char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR);
  TRACE_ALLOC_INF_FMT("file=%-15s line=%4d",
      fn? fn + 1:__zend_filename, __zend_lineno);
 }
#endif
 TRACE_ALLOC_INF_FMT("ptr=%p", ptr);

 ret = pemalloc_rel(REAL_SIZE(length + 1), persistent);
 {
  char * dest = (char *) FAKE_PTR(ret);
  memcpy(dest, ptr, length);
 }

 if (collect_memory_statistics) {
  *(size_t *) ret = length;
  MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_DUP_COUNT
                                        : STAT_MEM_EDUP_COUNT);
 }

 TRACE_ALLOC_RETURN(FAKE_PTR(ret));
}
/* }}} */
/* {{{ _mysqlnd_pememdup */
static char * _mysqlnd_pememdup(const char * const ptr,
              size_t length, bool persistent MYSQLND_MEM_D)
{
 char * ret;
 bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics);
 TRACE_ALLOC_ENTER(mysqlnd_pememdup_name);

#if PHP_DEBUG
 {
  const char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR);
  TRACE_ALLOC_INF_FMT("file=%-15s line=%4d",
      fn? fn + 1:__zend_filename, __zend_lineno);
 }
#endif
 TRACE_ALLOC_INF_FMT("ptr=%p", ptr);

 ret = pemalloc_rel(REAL_SIZE(length + 1), persistent);
 {
  char * dest = (char *) FAKE_PTR(ret);
  memcpy(dest, ptr, length);
 }

 if (collect_memory_statistics) {
  *(size_t *) ret = length;
  MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_DUP_COUNT
                                        : STAT_MEM_EDUP_COUNT);
 }

 TRACE_ALLOC_RETURN(FAKE_PTR(ret));
}
/* }}} */

For over 20 years, I patch security vulnerabilities in custom PHP code that should’ve never been there in the first place. Information leakage is often a first symptom of broader underyling negligence of security. Numerous security by obscurity implementations add to that.

PHP is considered an easy programming language for beginners. In the reality of running large production apps at scale, PHP is one of the most challenging beasts. What the programmers gain in ergonomics is what the administrators lose in operational excellence and reliability.

The future of PHP may not be

Apps outgrow PHP regularly, and predictably. Teams then start to pick the performance demanding parts first and migrate them to Rust or Go, sometimes even C++. PHP is an interpreted language, and thus needs more memory, compute and energy to interpret and execute your code. It simply can not execute as fast as AOT-compiled Rust, Go, or C++. The performance benchmarks almost always look the same when teams evaluate alternatives to their existing PHP implementations.

PHP’s advantage over C++ was always cost and time to market. It was simply more expensive to write a web app in C++ than it was writing the same thing in PHP. Programmers wrote PHP faster than they wrote C++. With Rust, Go, modern C++ and AI assistants, that advantage is long gone. Writing Rust or Go today is sometimes even faster than writing PHP.

Although PHP itself is a managed language, the underlying runtime is not. Today, Rust and Go can provide much better memory safety than PHP’s runtime environment does. Both languages also bring better tooling for modern cloud native event-driven microservices architectures. PHP no longer has any cost or security advantage over Rust or Go.

$Conclusion;

Programming languages don’t die, they slowly fade into obscurity. PHP is already an obscure language to anyone outside the PHP community. It’s popularity is on constant decline. PHP’s paradigms seemed outdated in the era of cloud computing and AI. It’s execution footprint has become a cost concern, too. The security challenges seemed to have become manageable, until today happened. AI driven security research has become a threat to PHP that it may not be able to survive much longer.

FORTRAN (released 1957) and COBOL (1959) are still around, and so will PHP (1993). But PHP is legacy software now. You can run PHP serverless in the cloud with Laravel Vapor, but so can you with GnuCOBOL. That doesn’t mean you should build a new application with PHP or COBOL, and let’s be honest, no one really does that. You use PHP because someone demanded WordPress, Typo3 or any other popular application written in PHP.

You don’t pick PHP, PHP picks you. People force it onto you, because they want a WordPress site, or whatever PHP app they’ve become emotionally attached to. But rising security issues, the increased cost to operate PHP, and its dated programming paradigms are slowly putting a final end to it now. A strong community, especially in Europe, will likely remain, but its brightest days are over. This disinterest was long shared by security researchers who simply didn’t have the time and budget to spend with PHP. But with the advancements in AI security research, that interest is back, and it’ll continue to cost the PHP maintainers dearly.

If you’re a PHP developer, it’s time to move on now. The best you can do for your apps and customers is to slowly migrate them to Rust or Go. PHP isn’t going to be rewritten in Rust overnight. Even if it would, the security issues in the design won’t magically disappear. I moved on from PHP over 15 years ago, and so can you. Don’t get slaughtered in the bloodbath.

Thank you for reading. Jan

All my articles are handicraft, handwritten by me. By reading and sharing my articles, you support my work and you support real human authorship.

I’m a software business owner from Germany, worked in various CTO roles, an active programmer in C++, Go, Swift and passionate technology enthusiast. My programming career started at a young age and I later acquired a professional institutional education in software engineering. My journey on Medium started out as note taking and documenting my projects. Over time, it became more and more popular with you, my beloved readers. Not because I am someone special, but because people crave for thoroughly researched technical articles. Following me, clapping and subscribing is one step forward in keeping technical writing and its community alive.