Your code can be clean, tested, and reviewed, and your server can still hand an attacker a shell. That is the uncomfortable lesson behind the PHP2Shell disclosure. The flaw did not live in WordPress, Laravel, or anyone's custom controller. It lived in the PHP engine itself, so even a plain "Hello World" script on an unpatched server was exposed.

Remote code execution (RCE) sits at the top of every severity scale for a simple reason. Once an attacker runs code on your box, they own whatever that process can touch: database credentials, API keys, uploaded files, session data, and often a path into the rest of your network.
This guide covers two fronts. First, the runtime layer, where you patch and verify the language engine. Second, the application layer, where PHP and Rails code opens its own doors through unsafe input handling. You need both.
What Is Remote Code Execution and Why Is It So Dangerous?
Most web vulnerabilities leak something. SQL injection might expose a table, and XSS might steal a cookie. RCE does something worse: it lets an outsider run instructions on your server as if they were your app.
From there, attackers usually follow a familiar playbook:
- Drop a web shell or backdoor so they can return later
- Read environment variables and config files for secrets
- Install crypto miners or ransomware
- Scan internal systems and move sideways to other servers
- Pull data out quietly over outbound connections
The damage depends on the permissions of the hijacked process. A PHP worker running as root turns a bad day into a total rebuild. A worker locked down to a single directory limits the blast radius.
What Is PHP2Shell (CVE-2026-109618 and CVE-2026-109619)?
PHP2Shell is the name for two critical PHP engine flaws, CVE-2026-109618 and CVE-2026-109619, which the PHP team patched on October 21. Researchers at Wiz found them with heavy help from frontier AI models, and the bugs trace back through more than a decade of PHP releases, as far as version 5.4.
Both issues come from how PHP cleans up after a fatal error, especially an out-of-memory crash, in the middle of a request. The engine can leave internal pointers aimed at memory it already freed. When the next request lands on the same worker, PHP trusts that stale state. An attacker who shapes that freed memory can turn the confusion into code execution or leak chunks of worker memory, including passwords and tokens from other users' requests.
A few details matter for site owners:
- No login required. The attacks work before your app's authentication code ever runs.
- App-agnostic. The memory leak path works even if your code never calls
session_start(). - Broad reach. PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP, and LiteSpeed setups all serve requests through the vulnerable code.
- Proven exploits. Researchers demonstrated pre-auth RCE against stock WordPress, Drupal, and Joomla containers.
The full write-up lives on the Wiz Research blog, and release notes sit on the official PHP site.
Which PHP Versions Are Affected and Which Are Fixed?
| PHP branch | Upgrade to at least |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
PHP 8.1 and older no longer get official security fixes. If you run one of those, check whether your OS vendor ships a backported patch, and start planning a move to a supported branch.
How to Check Which PHP Version Your Web Server Is Running
Many admins run php -v, see a good number, and stop. That only tells you about the command-line binary. Your web traffic may go through a different PHP-FPM pool or an Apache module on an older build.
Check the CLI first:
php -vThen check the FPM binary directly:
php-fpm8.4 -vThe most reliable test asks the web server itself. Drop a temporary file into your web root:
<?php
echo PHP_VERSION . ' / ' . PHP_SAPI;Load it in a browser, note the version and server API, and delete the file right away. Leaving diagnostic scripts on a live server gives attackers free reconnaissance.
How to Patch PHP on Ubuntu, Debian, Docker, and Shared Hosting
Ubuntu and Debian
Refresh your package lists and confirm the patched build is available:
sudo apt update
apt-cache policy php8.4Upgrade the core packages plus every extension your app uses:
sudo apt install --only-upgrade php8.4 php8.4-cli php8.4-common php8.4-fpm \
php8.4-mysql php8.4-curl php8.4-mbstring php8.4-xmlDistro packages sometimes carry backported fixes under an older-looking version string. When in doubt, read your vendor's security advisory instead of trusting the number alone.
Restart PHP-FPM and Reload the Web Server
A patched binary on disk does nothing until the old workers die. Restart the service and confirm it came back cleanly:
sudo systemctl restart php8.4-fpm
sudo systemctl status php8.4-fpmIf Nginx sits in front, test its config and reload:
sudo nginx -t && sudo systemctl reload nginxDocker Containers
Patching the host does not touch the PHP inside your containers. Each image carries its own runtime. Bump the base tag in your Dockerfile:
FROM php:8.4.27-fpmThen rebuild with a fresh pull and redeploy:
docker compose build --pull
docker compose up -d
docker compose exec app php -vRestarting an old container just restarts the old, vulnerable PHP. Keep the previous image tag handy in case you need to roll back.
Shared Hosting
On cPanel or similar panels, open the PHP version selector and switch to a patched build if your host offers one. If the host still lists only vulnerable versions, open a support ticket and ask for their patch timeline. Several large hosting and CDN providers rolled out edge mitigations, but those buy time. They do not replace the update.
How to Test Your Application After the PHP Security Update
A clean php -v does not mean a working site. Walk through the paths that break most often after a runtime update:
- Login, logout, and session persistence
- Database reads and writes
- File uploads
- Payment gateway and third-party API calls
- Cron jobs and queue workers
- Error logging (make sure stack traces stay out of public responses)
Run php -m under both CLI and FPM to confirm required extensions loaded, and run composer audit to flag known holes in your dependencies. Remember that composer audit checks libraries, not the engine, so it complements the runtime patch rather than replacing it.
How to Check If Your Server Was Exploited Before Patching
Patching closes the door. It does not tell you whether someone walked through it earlier. If your site sat exposed on a vulnerable version, look for these warning signs:
- Bursts of multipart POST requests followed by memory-limit errors or repeated HTTP 500 responses
- Oversized or garbled
PHPSESSIDcookies in responses - PHP workers restarting far more often than normal
- Shell processes spawned by
www-dataor your PHP user - New PHP files in upload, cache, or theme folders
- Unfamiliar cron entries or outbound connections to unknown IPs
Pull recent PHP-FPM logs and list running PHP processes:
sudo journalctl -u php8.4-fpm --since "48 hours ago"
ps aux | grep -E 'php-fpm|php-cgi'If you find real evidence of a breach, isolate the server, preserve logs, rebuild from a known-good source, and rotate every secret the PHP process could read: database passwords, API keys, SMTP credentials, and deploy tokens.
PHP Update Not Working? Common Problems and Fixes
PHP version did not change after the update. The package manager may have upgraded one PHP install while your web server still points at another. Compare php -v with php-fpm8.4 -v, then check which FPM socket your Nginx or Apache config uses.
PHP-FPM fails to start. Read the last 100 log lines with sudo journalctl -u php8.4-fpm -n 100, and validate the config with php-fpm8.4 -t. Missing extensions, invalid directives, and permission errors cause most failures.
The app reports missing extensions. A major or minor upgrade can leave extensions uninstalled. Install what you need, for example sudo apt install php8.4-mysql php8.4-curl, then confirm with php -m and restart PHP-FPM.
The site still runs an old PHP branch. If you keep several PHP versions installed, check the Apache handler or the Nginx fastcgi_pass socket. Point it at the patched version and restart the service.
How to Protect Rails Apps From Remote Code Execution
Rails developers did not inherit PHP2Shell, but Ruby apps have their own well-worn RCE routes. Most of them start in application code, not the interpreter.
Keep Ruby, Rails, and Gems Current
Run both of these in your project and in CI:
bundle update --conservative rails
gem install bundler-audit brakeman
bundle-audit check --update
brakeman -qbundler-audit flags gems with known CVEs. Brakeman scans your Rails code for dangerous patterns like unsafe render, eval, and shell calls. The Rails Security Guide covers the full list of risky APIs, and you should track Ruby releases on ruby-lang.org.
Never Deserialize Untrusted Data
Marshal.load on user-supplied data hands attackers code execution on a plate. The same goes for loading arbitrary YAML.
# Dangerous
obj = Marshal.load(params[:data])
config = YAML.unsafe_load(upload.read)
# Safer
data = JSON.parse(params[:data])
config = YAML.safe_load(upload.read, permitted_classes: [Date])Keep your secret_key_base secret too. If it leaks, attackers can forge signed cookies, and older cookie serializers that used Marshal turned that forgery into RCE. Use cookies_serializer = :json and rotate the key if it ever lands in a public repo.
Keep User Input Out of Shell Commands and Eval
String interpolation into a shell call is one of the fastest ways to lose a server:
# Dangerous
system("convert #{params[:file]} out.png")
# Safer: pass arguments as an array so no shell parses them
system("convert", params[:file], "out.png")Avoid eval, instance_eval, send with user-chosen method names, and constantize on raw params. Whitelist allowed values instead.
Don't Render User-Controlled Templates
render inline: with user content runs ERB, and ERB runs Ruby. Likewise, never pass params straight into render to pick a template or file path. Map user choices to a fixed list of allowed views.
Treat Uploads as Hostile
Store uploads outside the public folder or on object storage like S3. Validate content types by inspecting the file, not just the extension. If you process images, keep ImageMagick or libvips patched, because image libraries have a long history of RCE bugs triggered by crafted files.
Server Hardening Tips to Reduce RCE Damage in PHP and Rails
Some defenses shrink the damage no matter which bug an attacker uses:
- Run as a low-privilege user. Never run PHP-FPM, Puma, or Unicorn as root.
- Separate code from writable paths. Make your app directory read-only for the web user and give it write access only to uploads, cache, and temp folders.
- Block execution in upload folders. In Nginx, deny
.phphandling inside/uploads/. - Store secrets outside the codebase. Use environment variables or a secrets manager, and keep
.envfiles out of Git. - Restrict outbound traffic. Most web servers only need to reach a database, a mail relay, and a few APIs. Firewall everything else so a hijacked process cannot phone home freely.
- Add a WAF as a second layer. Cloudflare and similar services shipped PHP2Shell rules quickly, but a WAF filters known patterns. It cannot fix a broken runtime.
How to Automate PHP and Rails Security Checks in CI/CD
Emergency patching hurts most when you discover outdated software by accident. Bake a version gate into your deploy pipeline so builds fail on vulnerable runtimes:
php -r 'exit(version_compare(PHP_VERSION, "8.4.27", "<") ? 1 : 0);' \
|| { echo "PHP security update required"; exit 1; }For Rails, add bundle-audit check --update and brakeman --exit-on-warn as CI steps. Subscribe to the PHP, Ruby, and Rails security announcement lists, and set a clear rule for your team: internet-facing servers get critical runtime patches within a day, not at the next maintenance window.
Common Mistakes When Fixing RCE Vulnerabilities
| Mistake | What goes wrong | Fix |
|---|---|---|
Checking only php -v |
Web traffic still hits an old FPM or Apache module | Test the version through the web server |
| Patching the host, not containers | Containers keep running the old image | Rebuild images with --pull and redeploy |
| Skipping the FPM restart | Old workers keep serving requests | Restart PHP-FPM after every runtime update |
| Relying on a WAF alone | Attackers find payload variants the rules miss | Apply the official patch |
| Staying on PHP 8.1 or older | No upstream security fixes arrive | Upgrade to a supported branch |
| Ignoring past exposure | A backdoor survives the patch | Review logs and rotate secrets |
Using Marshal.load or YAML.unsafe_load on input in Rails |
Crafted payloads run Ruby code | Switch to JSON or YAML.safe_load |
Stay Protected: Patch First, Then Harden Your Code
PHP2Shell showed that secure application code cannot save you from an unpatched engine, and the fact that AI helped researchers turn a decade-old flaw into working exploits in days means attackers can move just as fast. Patch the runtime, confirm the version your web server really uses, test your critical flows, and check for signs of earlier compromise. Then close the application-level doors in your PHP and Rails code so the next engine bug has less to work with.
Treat security updates as part of shipping, not as a chore for later. The sites that recover fastest from the next big disclosure will be the ones that already know exactly what they run and how to update it in an hour.
Frequently Asked Questions
What is PHP2Shell?
PHP2Shell is the name for two critical flaws in the PHP engine, tracked as CVE-2026-109618 and CVE-2026-109619. They let an unauthenticated attacker run code on a server or read sensitive worker memory, and they affect the engine itself, so your application code does not need a bug for the attack to work.
Which PHP versions fix the PHP2Shell vulnerabilities?
Upgrade to PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35, or any later release on those branches. PHP 8.1 and older no longer receive official security fixes, so check your OS vendor for a backported patch or move to a supported branch.
Is running php -v enough to confirm my site is patched?
No. The command only shows the command-line binary. Your web server may still run an older PHP-FPM pool or Apache module, so check the version through the web server itself, restart PHP-FPM after the update, and rebuild any Docker images.
Can a web application firewall stop PHP2Shell?
A firewall can block known attack patterns and buy you time, but it cannot fix a flawed runtime. Attackers can craft payload variants that slip past rules, so apply the official PHP update as the real fix.
Are Rails apps affected by PHP2Shell?
No, PHP2Shell targets the PHP engine only. Rails apps face their own RCE risks, mainly unsafe deserialization with Marshal or unsafe YAML loading, user input inside shell commands, user-controlled templates, and outdated gems, so run bundler-audit and Brakeman and keep Ruby and Rails current.





