PHP2Shell, This Week

On 21st October 2026 the PHP project shipped four releases at once, 8.5.12, 8.4.27, 8.3.36 and 8.2.35, to fix two critical flaws in the interpreter itself1. Not in WordPress. Not in a plugin. In PHP.

Both are rated 9.2 on CVSS 4.0. Both allow remote code execution with no login. The researchers who found them put the reach plainly: “Every PHP version released in the last decade is affected by at least one of these vulnerabilities, going back to version 5.4.”2

The PHP project’s own advisory says the rest:

What the advisory saysWhat it means for you
“unauthenticated remote code execution”nobody needs an account on your site
“risk highest for mod_php”the most common old-style Apache setup is the worst placed
“before any application code runs”your own code, however careful, never gets a say
“There are no complete mitigations, updating is the only safe choice.”no config change saves you
“PHP 8.1 and older no longer receive updates”a lot of the web gets no fix at all

And it was being used before anyone was told. Amazon’s honeypot network recorded an attempt on 17th October, four days before disclosure3. watchTowr saw it in the wild on the 21st, and working proofs of concept were public within hours of the patch4.

Both bugs are memory-safety bugs. One is in how the interpreter cleans up after running out of memory. The other is a use-after-free: memory owned by one request is freed and then read again, and the next request on the same worker can see it56. Hold on to that second one. It comes back later.

None of this is new. That is the point of the post. PHP2Shell is not an accident that happened to a careful language. It is the latest page in a record nearly thirty years long, and every number in it is public.

One Process Holds Everything

I spent November 2014 to April 2017 as a third-line Linux engineer on Pulsant’s cloud and hosting platform. Part of that job was configuring PHP-FPM environments for e-commerce customers and tuning them to go fast.

PHP breaches were common. Not occasionally. Common. And they came down to two causes: poor deployment, and no separation between the front end and the back end.

The language makes the second one the default. A typical PHP site is one process. The page template, the business logic and the SQL all live in the same request handler, often in the same file. That process runs as one operating system user. It can read the database password, because the password is in a file next to the code. And it can usually write to its own web root, because that is how uploads and updates work.

One process holding everything, against tiers with their own boundariesThe usual PHP deploymentTiers with their own boundariesthe internetone worker, one OS userHTML templatesbusiness logicSQL, built in the same filewp-config.php: DB passworduploads/: writablethe code itself: writableone bug reaches all of itdatabase, full rightsthe internetfront endrenders pages, holds no credentialsback end serviceits own user, cannot write its codeanswers a narrow API onlydatabase, least privilege
Left, the usual PHP site: everything in one worker, one user, one bug away from all of it. Right, tiers with their own boundaries: the front end holds no credentials and the back end cannot rewrite itself.

That is not my opinion of how PHP gets deployed. It is what PHP and WordPress document.

SourceWhat it says
PHP manual, Apache modulewhen PHP runs as an Apache module “it inherits Apache’s user permissions”, and “a malicious script could access and modify the database, even without a username and password”7
PHP manual, Apache install“The mod_php approach embeds PHP directly into every Apache httpd worker process.”8
WordPress, wp-configthe file “is located in the root of your WordPress file directory and contains … database connection information”9
WordPress, file permissionswp-config.php “is initially created with 644 permissions, and it’s a hazard to leave it like that”10
WordPress, hardeningthe database user “only needs” SELECT, INSERT, UPDATE and DELETE, then “it is NOT recommended to revoke these privileges”11

Read that last row again. The vendor tells you what least privilege would look like, then tells you not to do it, because updates change the schema.

Compare the split version. The front end renders pages and holds no secrets. The back end is a separate service with its own user, a narrow API, and no write access to its own code. The database account can do what the back end needs and nothing else. OWASP’s guidance says the same thing in its own words: “The application’s backend database should be isolated from other servers”, on “a dedicated internal network segment”12. PHP’s own manual says applications “should never connect to the database as its owner or a superuser”13.

None of that is exotic. It is what a proper process looks like. The reason it matters here is simple. In the split model, a bug in the front end gets an attacker a front end. In the usual PHP model, a bug anywhere gets them the templates, the code, the credentials and the data, in one go.

PHP-FPM helps, and PHP’s own install guide now marks it as the recommended way to run PHP under Apache8. Its pools can start workers “with different uid/gid/chroot/environment”14. But a pool per site is still one site per process, holding everything that site has. It is a better wall between customers. It is not a wall inside the application.

Keep that picture in mind through every section below. Each bug in the record lands on that process.

Twenty-Nine Years Of CVEs

The National Vulnerability Database lists every CVE filed against the PHP interpreter under one product identifier, cpe:2.3:a:php:php. Ask it, and it answers 75715.

PHP interpreter CVEs per year, with the memory-corruption shareCVEs filed against the PHP interpreter, by year publishedall CVEs: 757memory corruption: at least 28840801201141201997200220072012201620212026
Every CVE against the PHP interpreter by year published, with the memory-corruption share overlaid. Counted from the NVD API on 23rd October 2026.

That count is the interpreter only. No WordPress, no Drupal, no plugins, no frameworks. Just the program that runs your PHP.

PeriodCVEsMemory corruptionWhat was going on
1997 to 2006933early web, little classification
200711419the Month of PHP Bugs
2008 to 201418754steady background
2015 to 2017208122the unserialize and memory years
2018 to 202310063fewer, still mostly memory
2024 to 20265527PHP2Shell lands in this window
Total757288

Two spikes stand out. The first is March 2007, when a former member of the PHP security team ran the Month of PHP Bugs, disclosing flaws in the Zend Engine, the PHP core and its extensions “on a day by day basis”16. He had founded the PHP Security Response Team, then left it, and said why: “I dislike the ways bugs are handled”17. When the founder of a project’s security team walks out and publishes a bug a day, that tells you how the inside was going.

The second spike is 2016: 120 CVEs, 65 of them memory corruption. More on that below.

The memory figure is a floor, not an estimate. NVD tags a good number of older bugs as “Other” rather than with a weakness type, including some of the use-after-frees in the next section. The real figure is higher. I am using the one I can show.

The Engine Is Written In C

This is where the memory-safety argument has to be made carefully, because a PHP developer will quite rightly point out that their code never calls malloc. PHP scripts do not manage memory by hand. You cannot write a buffer overflow in PHP the way you can in C.

You do not need to. The interpreter running your script is written in C, and its extensions are written in C. Every byte of untrusted input your site receives, every header, every upload, every form field, every cookie, is parsed by that C before your code sees it. When that C is wrong, your careful PHP does not matter. PHP2Shell proved it: the advisory says the attack can fire “before any application code runs”1.

The record shows what that C has got wrong:

CWE classWhat it isPHP interpreter CVEs
CWE-119memory buffer errors, general93
CWE-125out-of-bounds read50
CWE-190integer overflow35
CWE-189numeric errors, older tag33
CWE-416use after free27
CWE-787out-of-bounds write26
CWE-476null pointer dereference20

Those are the classes the security agencies mean when they talk about memory safety. In November 2022 the NSA advised organisations “to consider making a strategic shift from programming languages that provide little or no inherent memory protection, such as C/C++, to a memory safe language when possible”18. In December 2023 CISA, with the UK’s National Cyber Security Centre among the co-authors, put a number on it: “two-thirds of reported vulnerabilities in memory unsafe programming languages still relate to memory issues”19.

Neither document names PHP. They do not need to. They name the language PHP is built from.

The worst repeated example is unserialize(), the function that turns a stored string back into PHP objects. It has had the same class of bug, in the same place, more than once:

CVEWhereWhat NVD records
CVE-2014-8142process_nested_datause-after-free in unserialize20
CVE-2015-0231the same function“exists because of an incomplete fix for CVE-2014-8142”21
CVE-2015-2787__wakeup handlinguse-after-free in unserialize
CVE-2016-5771SPL, with garbage collectionuse-after-free in unserialize22
CVE-2016-5773ZipArchive, with garbage collectionuse-after-free in unserialize

The PHP manual knows. Its page for the function carries the warning: “Do not pass untrusted user input to unserialize() regardless of the options value of allowed_classes.”23 That is a language telling you one of its own built-in functions is not safe to use on the thing most web code handles all day.

Then there is PHP-FPM, the process manager PHP’s own install guide recommends over mod_php. In October 2019, CVE-2019-11043 showed that “in certain configurations of FPM setup it is possible to cause FPM module to write past allocated buffers … opening the possibility of remote code execution”24. The configuration in question was a common nginx setup. It was fixed on 24th October 201925. CISA later added it to its catalogue of vulnerabilities known to be exploited, flagged as used in ransomware campaigns26.

The PHP Foundation did commission a proper security audit of the interpreter’s source, and credit to it for that. The results, published on 10th April 2025, found 27 issues, 17 of them with security implications27. That is good practice and it should be normal. It is also 17 more, in an audit of a codebase that had already had nearly three decades of public bug reports.

The Language Made The Same Mistakes One Floor Up

The C is one layer. The language on top made the same kind of mistake in its design: trusting input it had no business trusting, and taking years to stop.

FeatureWhat it didDefault off or deprecatedRemovedTime
register_globalsturned request parameters into script variablesoff in 4.2.0, 22nd April 2002285.4.0, 1st March 201229ten years
magic quotesescaped input automatically, and wronglydeprecated 5.3.0, 30th June 20095.4.0, 1st March 201230three years
preg_replace /eevaluated the replacement as PHP codedeprecated 5.5.0, 20th June 2013317.0.0, 3rd December 201532two and a half years
allow_url_includelet include fetch code over the networkdeprecated 7.4.033
phar metadataunserialised automatically on file accessfixed in 8.034
0 == "foobar"compared truefixed in 8.035

register_globals deserves its own line. For years, any parameter in the request, ?admin=1 say, became a variable in your script, and if your code checked $admin before setting it, the attacker had set it for you. The PHP manual itself called switching it off “the most controversial change in PHP”29. Ten years from default-off to gone. That is how long the project tolerated a feature it knew was dangerous, because removing it would break sites.

The phar case is the same pattern a layer down. In 2018 Sam Thomas showed at Black Hat that ordinary file operations on a phar:// path would unserialise attacker-supplied metadata, bringing the unserialize() problem to functions nobody thought of as dangerous36. PHP 8.0, in late 2020, stopped doing it automatically, “to fix potential security vulnerabilities due to object instantiation, autoloading, etc.”34.

Then type juggling. PHP’s == converts types before comparing, so strings that look like numbers get compared as numbers. The RFC that fixed part of it in PHP 8.0 put it plainly: “This results in many surprising comparison results, the most notable of which is that 0 == “foobar” returns true.”35

That fix was real. It was also partial. Here is the same check run in the official php:7.4-cli and php:8.5-cli containers, the second being 8.5.12, the release patched for PHP2Shell this week, with the two outputs side by side:

$ php -r 'var_dump(0 == "foobar");'
bool(true)            # 7.4.33
bool(false)           # 8.5.12

$ php -r 'var_dump("0e462097431906509019562988736854" == "0e830400451993494058024219903391");'
bool(true)            # 7.4.33
bool(true)            # 8.5.12

Two different strings, equal. Both start 0e followed by digits, so PHP reads both as zero times ten to the power of something, and zero equals zero. Both are the shape of an MD5 hash. Compare a stored password hash with == instead of === or hash_equals(), and any input whose hash happens to start that way matches. It has been documented for years. It still works in the newest release.

That is the language in one example. A fix that went in, half of the problem left behind, and the safe version needing you to know a different operator exists.

A Shell, Nearly Every Year

The question worth asking of any platform is not how many bugs it has had. It is how often somebody could take the whole server without a password and without anyone clicking anything.

NVD scores every CVE. Filter the 757 for the score that means exactly that, network attack, no privileges, no user interaction, high impact on confidentiality and integrity, and you get 17215.

Unauthenticated full-compromise ratings per year, and confirmed exploitation per yearRated full remote compromise, no login, no user actionPHP interpreter CVEs, CVSS network, no privileges, no interaction, high impact: 172204060651997200220072012201620212026Confirmed exploited: PHP and PHP applications in CISA KEVentries added per year, 81 in all; the catalogue started in November 202116202116202210202382024112025202026
Top: interpreter CVEs scored as full remote compromise with no login and no user action. Bottom: PHP and PHP applications added each year to CISA’s catalogue of vulnerabilities known to be exploited.

172 across 23 of the 28 years that have any PHP CVE at all. Only five are blank. 2016 alone has 65.

A severity score is a rating, not a break-in. So the second half of the picture is the CISA Known Exploited Vulnerabilities catalogue, which only lists flaws with confirmed exploitation in the wild26. Of its 1,754 entries as of 21st October 2026, 81 are PHP itself or applications written in PHP:

Year addedPHP and PHP applicationsWhole catalogue
202116311
202216555
202310187
20248186
202511245
2026, to 21st October20270

2026 is already the worst year for PHP in the catalogue, and PHP2Shell is not in it yet. Three of this year’s are in WordPress core itself: two added on 21st July, and a remote file inclusion on 25th September.

KEV entries known to be used by ransomwareWhat
CVE-2019-11043PHP-FPM buffer overflow
CVE-2024-4577PHP-CGI argument injection
CVE-2021-3129Laravel Ignition
CVE-2018-7600, CVE-2018-7602Drupal

And the catalogue under-counts PHP badly. It is built around US federal systems and needs a CVE plus confirmed exploitation. It holds 3 WordPress plugin entries. The WordPress plugin ecosystem logged 11,334 vulnerabilities in 2025 alone37. The 81 is the part somebody official wrote down.

WordPress Multiplies It

WordPress is 39.6% of all websites, and 58.3% of the sites whose content management system can be identified38. PHP is the server-side language of 69.3% of the sites where that can be seen39. So whatever PHP’s record is, WordPress takes it to a very large part of the web, and then adds its own.

Core is not the main problem. The plugins are.

20242025
New WordPress ecosystem vulnerabilities, Patchstack7,9664011,33437
In plugins96%91%
In core76
Not fixed in time for public disclosure33%46%
Median time to mass exploitation, heavily exploited bugs5 hours

Wordfence, with its own database, counted 8,223 for 2024, a 68% rise on 2023, and found “Roughly 35% of the vulnerabilities disclosed in 2024 remain unpatched in 2025”41. Different databases, different totals, same shape.

Sit with the 2025 numbers for a moment. Nearly half of the vulnerabilities had no fix from the developer by the time they were made public. And for the ones attackers actually go after, the median gap from disclosure to mass exploitation is five hours. Most people do not check their plugin updates every five hours. Most people cannot.

The plugin model is the multiplier. A site’s security depends on the least careful author of anything installed on it, and Wordfence found “over one third of developers are unreachable or unresponsive” when told about a flaw41. Patchstack counted 1,614 plugins and themes pulled from the repository in 2024 for security issues nobody fixed40. And because of the one-process model, every plugin runs with the full rights of the site.

The incidents are a long list. These are the ones with primary sources behind them:

YearWhatScale
2011TimThumb, an image script bundled into many themes, let attackers “upload and execute arbitrary PHP code”42“many commercial and free themes”
2014RevSlider, behind the SoakSoak campaign43“compromising over 100,000 WordPress sites”
2016Mossack Fonseca, the Panama Papers firm: its site “may have been breached via a vulnerable version of Revolution Slider”44the site ran an exploitable version; how the data left was never shown
2017REST API content injection in WordPress 4.7.0 and 4.7.1, enabled by default4566,000 defaced pages indexed by Google on Sucuri’s count46; 1,893,690 on Wordfence’s four days later47
2020WP File Manager, unauthenticated code execution, exploited before a patch existed48“over 700,000 active installations”
2025SureTriggers, unauthenticated admin account creation49“exploitation attempts observed within hours after disclosure”

I have been careful with the 2016 row. Wordfence showed the firm’s site was running a slider version that was trivially exploitable. It did not show that was how the documents were taken, and I am not going to claim more than the evidence does.

How The Malware Stays

Getting in is half of it. Staying in is the other half, and PHP makes that easy for a reason that goes straight back to the one-process picture.

The interpreter will run any .php file it can reach. The web root is usually writable by the same user that runs the code. So once an attacker has any way to write a file, an upload form, a plugin bug, a file manager, they can write a script that runs every time somebody requests it. That is a web shell: a small file, sitting in your site, taking commands.

CISA has said for years which languages these are mostly written in: “The most commonly observed web shells are written in languages that are widely supported, such as PHP and ASP.”50 The NSA and the Australian Signals Directorate’s joint guidance calls them “a long-standing, pervasive threat that continues to evade many security tools”, and difficult to detect because they are “easily modified by attackers and often employ encryption, encoding, and obfuscation”51.

The clean-up numbers show how often they are there:

SourceFinding
Sucuri, 2022, 43,374 sites cleaned“69.63% of compromised websites were found to have at least one backdoor”; 1,188,864 backdoors removed52
Sucuri, 2022“90% of credit card skimmers were found in the form of malicious PHP code”52
Sucuri, 2023, 39,594 sites cleaned“49.21% of compromised websites were found with at least one website backdoor”; WordPress 95.5% of infections53
Wordfence, 2024“just under one million distinct sites infected with malware”41

So in the most recent clean-up year, half the hacked sites still had a way back in sitting on disk. Restore the database, patch the plugin, and the attacker walks back through the file they left.

There is no clever fix for this. There is the boring, proper one, and the NSA wrote it down: “web applications should not have permission to write directly to a web accessible directory or modify web accessible code”51. That single sentence is the split-tier model from the start of this post, said by an intelligence agency.

Where it goes wrongThe control that stops it
the runtime user can write the codecode owned by a separate deploy user, read-only to the runtime user
uploads land inside the web rootstore uploads “outside of the webroot”, or on another server54
PHP will execute anything in uploadsno script execution for upload paths at the web server, and “disable per directory configuration overrides on upload directories”54
nobody notices a new filefile integrity monitoring against a known-good copy51
the database account can do anythingleast privilege on the database user13

WordPress’s hardening guide, for what it is worth, never mentions stopping PHP from running in the uploads directory at all11. That is why the table cites OWASP and the NSA instead.

Notice what is not in that table. PHP’s own hardening settings. The manual is honest about them. disable_functions “can be circumvented and should not be considered a sufficient security measure for shared hosting environments”, and open_basedir “is just an extra safety net, that is in no way comprehensive, and can therefore not be relied upon when security is needed”55. When a language’s own documentation tells you its security knobs are not security boundaries, believe it. As such, the boundary has to come from outside the process.

Even php.net Got Broken Into

If the project’s own infrastructure were solid, you could argue the record is down to a hard problem handled well. It has been broken into twice.

WhenWhat happened
22nd to 24th October 2013“JavaScript malware was served to a small percentage of php.net users”; “two servers were compromised”, method “unknown at this time”56
28th March 2021“two malicious commits were pushed to the php-src repo” under the names of two core developers57

The 2021 one is the one that matters. Somebody pushed code into the language’s own source tree, impersonating the people who maintain it. It was caught. The project’s answer is worth quoting because it is the right one, said plainly: “maintaining our own git infrastructure is an unnecessary security risk”, and the repositories moved to GitHub57.

Credit where it is due: that was a good decision, made fast, and explained in the open. But look at what it admits. For years, the canonical source of a language running most of the web lived on self-run infrastructure that the project itself decided was a risk it should not be carrying.

Half The Web Runs A Version Nobody Patches

All of the above assumes the site is up to date. A great many are not.

php.net currently supports four branches. 8.2 gets security fixes only, until 31st December 2026. 8.3, 8.4 and 8.5 are the rest58. Everything older is end of life: 8.1 since 31st December 2025, 8.0 since November 2023, 7.4 since November 202259.

Now the measured share of what is actually running:

How much of PHP runs on versions php.net no longer patchesPHP version in use, by php.net support statusPHP sites (W3Techs)48.3%18.0%33.7%WordPress installs34.7%23.5%41.8%unsupported now8.2, unsupported from 31st December 20268.3 and later
PHP version in use by support status. PHP sites from W3Techs’ version shares; WordPress installs from wordpress.org’s own statistics. Both read on 23rd October 2026.
Unsupported todayUnsupported from 1st January 2027
PHP sites, W3Techs606148.3%about 66%
WordPress installs, wordpress.org6234.7%about 58%

PHP 7.4 alone, out of support for nearly four years, is about a fifth of all PHP sites that show their version. WordPress’s own requirements page asks for PHP 8.3, then says WordPress “also works with PHP 7.4+”, while noting those versions “have reached their official End Of Life”63. A vendor that still runs on a dead version is a vendor that has decided not to make you move.

Two honest caveats. W3Techs only sees the version on sites that advertise it. And some Linux distributions backport fixes into old PHP builds, so “unsupported by php.net” is not always “unpatched”. But PHP2Shell is the test of that. The project did publish source patches for 7.4, 8.0 and 8.1 this time1, and someone still has to build them, ship them and install them. On a shared host running a version nobody owns, that someone is nobody.

I Gave Up On It. You Should Look At Leaving Too.

I used to write PHP. After Pulsant I gave up on the language, because of the issues it caused and the work those issues made. Not one bad incident. Years of the same two causes, on site after site, and the clean-up landing on whoever was on shift.

So I will say it plainly. If the choice is yours, look at leaving it. Better tools exist for this now, and Go is the one I would point you at first.

Not because it is fashionable. Because it removes, by default, most of what this post has been counting:

The usual PHP siteA Go service
Memory safetyengine written in C, 288 memory-corruption CVEsmemory safe by design; the NSA and CISA both name it1819
Running the codeparsed and compiled again on every request unless OPcache is loaded, an extension you add yourself before 8.5; then run by an interpreter written in C; a JIT since 8.0, off by default6465compiled once, ahead of time, to native machine code66
Serving a requestthe web server hands each request to an interpreterone compiled binary answers HTTP itself
A file dropped in the web roota .php file runs the moment somebody requests itnothing runs a file from disk because of its path
HTML outputecho writes raw; escaping is a function you have to rememberhtml/template escapes by default, by context67
SQLprepared statements exist in PDO, if you use themthe documented way passes parameters as arguments68
Known flaws in what you depend onfind out from the newsgovulncheck reports only the ones your code actually calls69
Where the pieces livecode, config and uploads in one writable treea read-only binary, with config and uploads somewhere else

Compiled matters twice. It is faster: Go programs “are compiled ahead of time to native machine code”, with no virtual machine underneath, and one of its stated design goals is “to approach the performance of C for comparable programs”66. And it is more secure, because there is no interpreter in the request path parsing code at run time, and no C engine of 757 CVEs standing between the network and your program. PHP, left to itself, does the opposite. Without OPcache it loads, parses and compiles every script on every request, needlessly, thousands of times a minute on a busy site. OPcache exists to stop that, “removing the need for PHP to load and parse scripts on each request”64. But it is not on out of the box. Up to and including 8.4 it is a separate shared extension that somebody has to load with zend_extension and configure. Only PHP 8.5 builds it in: “The Opcache extension is now always built into the PHP binary and is always loaded”70. The project’s own RFC for that change says “running PHP in production without OPcache is generally considered a mistake”71. W3Techs puts 8.5 on 4.5% of PHP 8 sites60, so for nearly everything running today the fix for compiling every script on every request is still an add-on somebody had to remember. And even loaded, what it caches is bytecode for the C interpreter to run. Go does the compiling once, before the program ever sees a request.

Faster and more secure, provided it is coded properly. That last clause is the whole of the next few paragraphs.

Go’s template package puts its design in one line: it generates “HTML output safe against code injection”, and “The escaping is contextual, so actions can appear within JavaScript, CSS, and URI contexts.”67 Its database guide is just as plain: “You can avoid an SQL injection risk by providing SQL parameter values as sql package function arguments.”68 Those are the defaults. In PHP they are things you have to know to go and get.

The row I would underline is the dropped file. Half the hacked sites Sucuri cleaned in 2023 still had a backdoor on disk53, and that only works because the interpreter will run any script it can reach. A compiled service does not go looking for scripts. There is nowt for a dropped file to plug into.

Now the honest part, because a language is not a cure. Go is more memory safe, not perfectly so. Its own memory model says a data race on a slice, a map or an interface value “can in turn lead to arbitrary memory corruption”72, and Go ships the tool to catch it. Build with -race, or run go test -race, and the binary is compiled with a detector that reports a race the moment one happens. It catches races at run time rather than at compile time, and “can’t find races in code paths that are not executed”, so it belongs in every test run and under a realistic load73. PHP has nothing like it. Keeping one request’s memory away from the next is left entirely to the engine, and PHP2Shell is that engine getting it wrong: memory freed by one request, read by the next on the same worker6. Outside that, Go stops the memory-corruption class this post has been counting, and makes the safe way the easy way for two more. It does not stop a broken login check, a missing permission test or a query built by gluing strings together if you insist on doing that. Go publishes its own security advisories too, through its vulnerability database69. And a Go service run as root out of a writable directory is still badly deployed.

What it changes is the default. PHP’s default is one process holding everything. Go’s default is a program that does one job, which is the split from the start of this post with most of the work already done. If Go does not suit your team, Rust is the other way out. Both compile ahead of time to native code. Rust runs “with no runtime or garbage collector”74, and Go’s compiler and runtime have been “implemented in Go and assembler, without C” since Go 1.575.

C# and Java are on the agencies’ memory-safe list too18, but they are not a way out of the problem this post started with. Each runs on a virtual machine written in C++. OpenJDK’s HotSpot is “around 3000 C/C++ header and source files, comprising more than 1.2 million lines of code”76, and .NET’s garbage collector and JIT compiler are C++ source in the CoreCLR tree77. That is the same shape as PHP: a large native engine between the network and your code. And Java loads code into a running application by design. Its standard class loader exists “to load classes and resources from a search path of URLs referring to both JAR files and directories”78, and Log4Shell turned that into remote code execution: an attacker who controlled a log message “can execute arbitrary code loaded from LDAP servers”79. It went straight into CISA’s catalogue26. It is the same door as PHP’s, with more ceremony in front of it.

So pick Go or Rust. Move the back end first, one endpoint at a time, and leave the PHP front end holding nothing worth stealing until it can go as well.

If You Are Still Deploying It This Way, Can You Call Yourself A Professional?

There is enough evidence above that this has stopped being an opinion. So here is the question, put directly. If you are still deploying PHP as one process that holds the code, the credentials and a writable web root, can you call yourself a professional?

It depends on which of three people you are. And knowing which one is not something that happens to you. Making sure you know is the job.

You have a real constraint, and you have written it down. A product the business depends on only exists in PHP. A migration is costed and in next year’s budget. Fine. That is professional, provided the reason is on paper with a date next to it, the runtime user cannot write the code, uploads cannot execute, the database account is cut down, and you patched PHP2Shell this week. You are running a known risk on purpose, with the walls you can put round it.

You have never been told. Nobody showed you the record, and the tutorial you learnt from put everything in one directory. That is a gap, and everybody has gaps. It stops being a gap the moment you have read this far.

You know, and you build it anyway. You have seen the numbers, or enough of them. You know the database password sits in the web root and the web root is writable. You deploy it like that because it is quicker, because the client will not pay for better, and because nobody has made you justify it. That is the case this question is aimed at. The failing is not the gap in your knowledge. It is building across a gap you have already seen and never went back to close.

Everybody was there once. Staying there by choice is the part that is not defensible.

The Door Was Always Open

What strikes me reading this record is not that PHP has bugs. Every language does. It is how little of it was ever a surprise.

The project knew register_globals was dangerous in 2002 and removed it in 2012. Its manual tells you not to trust unserialize(), not to trust disable_functions, not to trust open_basedir. Its own documentation says a script under mod_php can reach the database without a password. The agencies have been saying for years that the language it is built from is where two-thirds of the memory bugs come from. Every warning is there, in writing, from the people who should know.

And it kept winning anyway, because it was cheap. Cheap to host, cheap to learn, cheap to sell to a customer who wanted a website by Friday. One directory, one process, one password in a file, and a site that worked. What it was priced at was a shared hosting plan. What it cost was never on the invoice: the clean-ups, the backdoors left on disk, the customers’ card numbers lifted by malicious PHP, the five-hour window between disclosure and the attack.

That is the bit I have no patience for. Not the bugs. The decision, made over and over by people who knew better, that the cheap way was good enough because nobody was checking. A standard is the thing you keep when nobody is checking. The split between front end and back end, the read-only code, the cut-down database account: none of it is new, none of it is expensive, and all of it would have turned a good many of the incidents above into nowt worth writing about.

PHP2Shell is not the end of this record. It is just this week’s entry. The language will patch it, the hosts will roll out the update eventually, and the version share will drift up the way it always does, slowly. What decides whether the next one costs you is not PHP. It is whether you built the walls it never gave you, or moved to something that comes with them.


  1. PHP — News archive 2026, 21st October — “PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical vulnerabilities that can allow unauthenticated remote code execution”; “We consider the risk highest for mod_php, with a demonstrated exploit where an unauthenticated request can trigger the attack before any application code runs”; “There are no complete mitigations, updating is the only safe choice.” ↩︎ ↩︎ ↩︎

  2. Wiz Research — PHP2Shell — 21st October 2026; “Every PHP version released in the last decade is affected by at least one of these vulnerabilities, going back to version 5.4”; “All of the disclosed vulnerabilities are memory-safety issues”. ↩︎

  3. AWS Security Blog — PHP2Shell exploitation prior to public disclosure in MadPot — an exploitation attempt recorded on 17th October 2026. ↩︎

  4. watchTowr — PHP2Shell FAQ — exploitation observed in the wild on 21st October 2026. ↩︎

  5. NVD — CVE-2026-109618 — out-of-memory error handling leaves partly initialised request data that cleanup treats as valid; remote code execution. ↩︎

  6. NVD — CVE-2026-109619 — request memory used after it has been freed; remote code execution; affected from 5.4.0. ↩︎ ↩︎

  7. PHP manual — Installed as an Apache module — “When PHP is used as an Apache module it inherits Apache’s user permissions”; “a malicious script could access and modify the database, even without a username and password”. ↩︎

  8. PHP manual — Apache 2.x on Unix systems — “The mod_php approach embeds PHP directly into every Apache httpd worker process.” ↩︎ ↩︎

  9. WordPress — wp-config.php — located in the root of the WordPress directory and holds the database connection information. ↩︎

  10. WordPress — Changing file permissions — wp-config.php “is initially created with 644 permissions, and it’s a hazard to leave it like that”. ↩︎

  11. WordPress — Hardening WordPress — database privileges, file permissions; no guidance on blocking PHP execution in uploads. ↩︎ ↩︎

  12. OWASP — Database Security Cheat Sheet — “The application’s backend database should be isolated from other servers”; a dedicated internal network segment. ↩︎

  13. PHP manual — Database design — “Applications should never connect to the database as its owner or a superuser.” ↩︎ ↩︎

  14. PHP manual — FastCGI Process Manager — pools that start workers “with different uid/gid/chroot/environment”. ↩︎

  15. NVD CVE API, virtualMatchString=cpe:2.3:a:php:php — queried 23rd October 2026: 757 CVEs. Memory corruption counted as CWE-119, 120, 121, 122, 125, 129, 131, 189, 190, 191, 415, 416, 476, 680, 787, 824, 843 or 908: 288. Full remote compromise counted as CVSS 3.x AV:N/PR:N/UI:N/C:H/I:H, or CVSS 2 network, no authentication, complete confidentiality and integrity impact where no 3.x score exists: 172. ↩︎ ↩︎

  16. Month of PHP Bugs, php-security.org, Wayback Machine — “During March 2007 old and new security vulnerabilities in the Zend Engine, the PHP core and the PHP extensions will be disclosed on a day by day basis.” ↩︎

  17. The Register — Stefan Esser interview, 7th February 2007 — founder of the Hardened-PHP Project, recently left the PHP Security Response Team: “I dislike the ways bugs are handled”. ↩︎

  18. NSA — Software Memory Safety, November 2022, Wayback Machine — “a strategic shift from programming languages that provide little or no inherent memory protection, such as C/C++, to a memory safe language when possible.” Also: “Examples of memory safe language include C#, Go, Java®, Ruby™, Rust®, and Swift®.” ↩︎ ↩︎ ↩︎

  19. CISA and partners — The Case for Memory Safe Roadmaps, December 2023 — co-authored with the UK NCSC among others; “two-thirds of reported vulnerabilities in memory unsafe programming languages still relate to memory issues.” ↩︎ ↩︎

  20. NVD — CVE-2014-8142 — use-after-free in process_nested_data during unserialize. ↩︎

  21. NVD — CVE-2015-0231 — use-after-free in unserialize that “exists because of an incomplete fix for CVE-2014-8142”. ↩︎

  22. NVD — CVE-2016-5771 — SPL use-after-free triggered through unserialize and garbage collection. ↩︎

  23. PHP manual — unserialize — “Do not pass untrusted user input to unserialize() regardless of the options value of allowed_classes.” ↩︎

  24. NVD — CVE-2019-11043 — “in certain configurations of FPM setup it is possible to cause FPM module to write past allocated buffers … opening the possibility of remote code execution.” ↩︎

  25. PHP 7 ChangeLog — fixed on 24th October 2019 in 7.3.11, 7.2.24 and 7.1.33, bug #78599. ↩︎

  26. CISA — Known Exploited Vulnerabilities catalogue — JSON feed version 2026.10.21, 1,754 entries; 81 for PHP or applications written in PHP by vendor and product; ransomware use as recorded in the feed. ↩︎ ↩︎ ↩︎

  27. The PHP Foundation — PHP core security audit results, 10th April 2025 — “The audit identified 27 issues, with 17 having security implications”. ↩︎

  28. PHP 4 ChangeLog — 4.2.0, 22nd April 2002: “register_globals defaults to ‘off’ now”. ↩︎

  29. PHP manual — Using Register Globals, Wayback Machine — “DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0”; “the most controversial change in PHP”. ↩︎ ↩︎

  30. PHP manual — Magic Quotes, Wayback Machine — “DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0”. ↩︎

  31. PHP manual — Deprecated features in PHP 5.5.x, Wayback Machine — “The preg_replace() /e modifier is now deprecated.” ↩︎

  32. PHP manual — Pattern Modifiers, Wayback Machine — e “DEPRECATED in PHP 5.5.0, and REMOVED as of PHP 7.0.0.” ↩︎

  33. PHP manual — Deprecated features in PHP 7.4 — “The allow_url_include ini directive is deprecated.” ↩︎

  34. PHP manual — Backward incompatible changes in PHP 8.0 — phar metadata “will no longer be automatically unserialized, to fix potential security vulnerabilities due to object instantiation, autoloading, etc.” ↩︎ ↩︎

  35. PHP RFC — Saner string to number comparisons — implemented in PHP 8.0; “the most notable of which is that 0 == “foobar” returns true.” ↩︎ ↩︎

  36. Sam Thomas, Black Hat USA 2018 — It’s A PHP Unserialization Vulnerability Jim, But Not As We Know It — file operations on phar:// paths unserialise metadata. ↩︎

  37. Patchstack — State of WordPress Security in 2026 — 2025 data: 11,334 new vulnerabilities, 91% plugins, 6 in core, 46% not fixed in time for disclosure, median 5 hours to mass exploitation. ↩︎ ↩︎

  38. W3Techs — WordPress — “58.3% of all the websites whose content management system we know. This is 39.6% of all websites.” ↩︎

  39. W3Techs — PHP — “PHP is used by 69.3% of all the websites whose server-side programming language we know.” Read 23rd October 2026. Major versions among PHP sites: 8 is 65.3%, 7 is 26.9%, 5 is 7.7%. ↩︎

  40. Patchstack — State of WordPress Security in 2025 — 2024 data: 7,966 new vulnerabilities, 96% plugins, 7 in core, 33% not fixed in time for disclosure. ↩︎ ↩︎

  41. Wordfence — 2024 Annual WordPress Security Report — 8,223 vulnerabilities, a 68% increase; “Roughly 35% of the vulnerabilities disclosed in 2024 remain unpatched in 2025”; “just under one million distinct sites infected with malware”. ↩︎ ↩︎ ↩︎

  42. Mark Maunder — Zero day vulnerability in many WordPress themes, 1st August 2011 — timthumb.php bundled with “many commercial and free themes”; attackers could “upload and execute arbitrary PHP code”. ↩︎

  43. Sucuri — RevSlider vulnerability leads to massive WordPress SoakSoak compromise, 15th December 2014 — “compromising over 100,000 WordPress sites”. ↩︎

  44. Wordfence — Mossack Fonseca breach, Revolution Slider possible cause, 7th April 2016, Wayback Machine — “may have been breached via a vulnerable version of Revolution Slider”. ↩︎

  45. Sucuri — Content injection vulnerability in WordPress REST API, 1st February 2017 — “allows an unauthenticated user to modify the content of any post or page”; enabled by default on 4.7.0 and 4.7.1. ↩︎

  46. Sucuri — WordPress REST API vulnerability abused in defacement campaigns, 6th February 2017 — “Google alone shows 66,000+ pages compromised”, for one campaign. ↩︎

  47. Wordfence — Rapid growth in REST API defacements, 10th February 2017, Wayback Machine — defaced pages indexed by Google grew “from 1,496,020 to 1,893,690”; Sucuri’s earlier count was 66,000. ↩︎

  48. Wordfence — 700,000 WordPress users affected by zero-day in File Manager plugin, 1st September 2020, Wayback Machine — “a WordPress plugin with over 700,000 active installations”. ↩︎

  49. Patchstack — SureTriggers vulnerability exploited within 4 hours, 10th April 2025 — unauthenticated administrator creation; “exploitation attempts observed within hours after disclosure”. ↩︎

  50. CISA — Compromised Web Servers and Web Shells, TA15-314A — “The most commonly observed web shells are written in languages that are widely supported, such as PHP and ASP.” ↩︎

  51. NSA and ASD — Detect and Prevent Web Shell Malware, April 2020, Wayback Machine — “web applications should not have permission to write directly to a web accessible directory or modify web accessible code”; file integrity monitoring as the fallback. ↩︎ ↩︎ ↩︎

  52. Sucuri — 2022 Website Threat Research Report — 43,374 sites cleaned; 69.63% with at least one backdoor; 1,188,864 backdoors removed; 90% of card skimmers in malicious PHP. ↩︎ ↩︎

  53. Sucuri — 2023 Hacked Website and Malware Threat Report — 39,594 sites cleaned; 49.21% with at least one backdoor; WordPress 95.5% of infections. ↩︎ ↩︎

  54. OWASP — File Upload Cheat Sheet — store uploads “outside of the webroot”; “disable per directory configuration overrides on upload directories”. ↩︎ ↩︎

  55. PHP manual — Description of core php.ini directives — disable_functions “can be circumvented”; open_basedir “can therefore not be relied upon when security is needed”. ↩︎

  56. PHP — News archive 2013, 24th October — malware served from 22nd to 24th October 2013; two servers compromised. ↩︎

  57. PHP internals — Changes to Git commit workflow, 28th March 2021 — two malicious commits pushed to php-src; the git.php.net server discontinued and GitHub made canonical. ↩︎ ↩︎

  58. PHP — Supported versions — 8.2 security fixes until 31st December 2026; 8.3, 8.4 and 8.5 supported. Read 23rd October 2026. ↩︎

  59. PHP — Unsupported branches — 8.1 end of life 31st December 2025, 8.0 26th November 2023, 7.4 28th November 2022. ↩︎

  60. W3Techs — PHP version 8 — subversion shares within PHP 8; combined with the major-version shares to give 48.3% of PHP sites on unsupported versions. ↩︎ ↩︎

  61. W3Techs — PHP version 7 — 7.4 is 73.4% of PHP 7 sites. ↩︎

  62. WordPress — Statistics — PHP versions of WordPress sites reporting to wordpress.org; 34.7% below 8.2. Read 23rd October 2026. ↩︎

  63. WordPress — Requirements — “PHP version 8.3 or greater”; works with 7.4+, which has “reached their official End Of Life”. ↩︎

  64. PHP manual — OPcache introduction — “OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request.” Up to PHP 8.4, a shared extension loaded through zend_extension. ↩︎ ↩︎

  65. PHP manual — OPcache runtime configuration — opcache.jit available as of PHP 8.0.0, default “disable”. ↩︎

  66. Go — Frequently Asked Questions — “Go programs are compiled ahead of time to native machine code”; the runtime “does not include a virtual machine”; “One of Go’s design goals is to approach the performance of C for comparable programs”. ↩︎ ↩︎

  67. Go — package html/template — “implements data-driven templates for generating HTML output safe against code injection”; “The escaping is contextual, so actions can appear within JavaScript, CSS, and URI contexts.” ↩︎ ↩︎

  68. Go — Avoiding SQL injection risk — “You can avoid an SQL injection risk by providing SQL parameter values as sql package function arguments.” ↩︎ ↩︎

  69. Go — Vulnerability management — a curated vulnerability database; govulncheck “only surfaces vulnerabilities that actually affect you, based on which functions in your code are transitively calling vulnerable functions”. ↩︎ ↩︎

  70. PHP manual — Backward incompatible changes in PHP 8.5 — “The Opcache extension is now always built into the PHP binary and is always loaded. The INI directives opcache.enable and opcache.enable_cli are still honored.” ↩︎

  71. PHP RFC — Make OPcache a non-optional part of PHP — implemented in PHP 8.5; “running PHP in production without OPcache is generally considered a mistake”. ↩︎

  72. Go — The Go Memory Model — races on multiword values “can in turn lead to arbitrary memory corruption”; ThreadSanitizer via go build -race reports races. ↩︎

  73. Go — Data Race Detector — run tests with go test -race; “The race detector only finds races that happen at runtime, so it can’t find races in code paths that are not executed.” ↩︎

  74. Rust — rust-lang.org — “Rust is blazingly fast and memory-efficient: with no runtime or garbage collector”. ↩︎

  75. Go 1.5 Release Notes — “The compiler and runtime are now implemented in Go and assembler, without C.” ↩︎

  76. OpenJDK — The HotSpot Group — “around 3000 C/C++ header and source files, comprising more than 1.2 million lines of code”. ↩︎

  77. dotnet/runtime — src/coreclr — the garbage collector (gc/gc.cpp) and the JIT (jit/compiler.cpp) are C++. ↩︎

  78. Java SE 21 — URLClassLoader — “to load classes and resources from a search path of URLs referring to both JAR files and directories”. ↩︎

  79. NVD — CVE-2021-44228 — “An attacker who can control log messages or log message parameters can execute arbitrary code loaded from LDAP servers”; added to CISA KEV on 10th December 2021. ↩︎