Back to blog

PHP2Shell (CVE-2026-109618, CVE-2026-109619): How to Patch PHP and Recover a Hacked Site

IM Host EditorialOctober 22, 202613 min read
PHP2Shell (CVE-2026-109618, CVE-2026-109619): How to Patch PHP and Recover a Hacked Site

On October 21, 2026, PHP shipped emergency releases for two flaws that let an attacker run code on a web server without logging in. They are tracked as CVE-2026-109618 and CVE-2026-109619, are known together as PHP2Shell, and are rated CVSS 9.2. Attempts to exploit them were seen on October 17, four days before the fix was public.

This guide covers what the two bugs are, how to check whether you are affected, how to patch on the setups most sites run, what to do if you can't patch today, and how to recover cleanly if a site was compromised before you patched.

The short version

  • Update PHP now to 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or your distribution's patched package), then restart every PHP process.

  • Every way of running PHP for the web is affected: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed.

  • PHP 7.4 to 8.1 only received minimal source backports. PHP 5.4 to 7.3 is end-of-life and gets no fix at all.

  • Temporary settings reduce exposure but are not a fix. Patching is the only complete remedy.

  • If a site stayed unpatched after October 17, check it for signs of compromise. If you find any, restore from a backup taken before that date, and only after PHP is patched.

What happened: the timeline

  • October 17: Amazon Threat Intelligence observed an exploitation attempt against its honeypots, before any public disclosure.

  • October 20: The CVE identifiers started circulating, and the PHP project announced an early release.

  • October 21, 14:00 UTC: PHP published the fixed versions and its advisory. On the same day, Amazon, Cloudflare and watchTowr reported more exploitation attempts, some from different actors.

Some reports describe public proof-of-concept code, but none had been verified as working at the time of writing. That changes quickly with bugs this widely deployed, so the safe assumption is that working exploits exist or will soon.

The two vulnerabilities in plain language

CVE-2026-109618: corruption when memory runs out

When PHP runs out of memory while it is still setting up a request, it can stop halfway. The cleanup step that follows then treats half-initialised data as if it were valid. An attacker can trigger this with a crafted multipart request (the kind used by forms and file uploads), even one that appears to stay within configured size limits.

On Apache with mod_php, this cleanup runs even when the request fails to start, so the attack works before your application code runs at all. That is why the PHP team rates mod_php as the highest-risk setup.

CVE-2026-109619: leftover state between requests

This one is a use-after-free. Internal state from one request can still point to memory that was freed when that request ended, and a later request handled by the same worker can use it. PHP has confirmed both code execution and memory disclosure from this bug. Two paths were demonstrated:

  • Session upload progress, which is enabled by default and does not require your application to call session_start(). This was confirmed against both mod_php and PHP-FPM.

  • Fatal error backtraces in PHP 8.5 (fatal_error_backtraces, also on by default), confirmed against mod_php.

The researchers who reported the flaws say they achieved unauthenticated code execution against specific versions of WordPress, Drupal, Joomla and Magento. Hiding errors with display_errors, error_reporting or the @ operator does not stop either bug.

Step 1: Find every PHP you run

Many servers run more than one PHP version: one for the command line, others for different sites. The version that matters is the one serving web requests, so check all of them.

# Command-line version
php -v

# Installed PHP packages (Debian / Ubuntu)
dpkg -l | grep -E '^ii +php[0-9.]+-(fpm|cli|common)'

# Installed PHP packages (AlmaLinux / Rocky / RHEL)
rpm -qa | grep -i php

# What is actually running
ps aux | grep -E 'php-fpm|php-cgi|lsphp|frankenphp' | grep -v grep

# Is Apache loading mod_php?
apache2ctl -M 2>/dev/null | grep -i php || httpd -M 2>/dev/null | grep -i php

On cPanel servers, MultiPHP Manager lists the version assigned to each domain. In Docker, check each container with docker exec <container> php -v.

Don't judge by the version number alone. Distributions such as Ubuntu, Debian and RHEL often fix security bugs inside an older version number. A package that reports 8.3.6 can be patched, and one that reports a newer number can still be vulnerable. Check your distribution's security notice or package changelog for the CVE number.

Step 2: Patch, restart, verify

Ubuntu and Debian

sudo apt update
sudo apt upgrade

# Confirm the fix is in the package you installed (adjust the version)
apt changelog php8.3-common | grep -m3 'CVE-2026-10961'

# Restart PHP and the web server
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2   # or nginx

If you use the Ondřej Surý PPA or another third-party repository, it is updated separately from the distribution, so make sure it has published the fixed build before you assume you are covered.

AlmaLinux, Rocky Linux and RHEL

sudo dnf update 'php*'
rpm -q --changelog php-common | grep -m3 'CVE-2026-10961'
sudo systemctl restart php-fpm httpd

PHP's advisory noted that the Remi repository would not have updates on release day, and recommended distribution packages or building from source in the meantime. If you rely on Remi, confirm a fixed package exists before you move on.

cPanel / WHM

cPanel ships PHP as ea-php packages. Update them and check MultiPHP Manager afterwards:

sudo dnf update 'ea-php*'

Then restart PHP-FPM from WHM, or restart Apache if a domain uses a non-FPM handler.

Docker

A running container does not update itself. Pull the patched base image, rebuild your image on top of it, and replace the running container:

docker compose build --pull
docker compose up -d
docker compose exec app php -v

LiteSpeed and FrankenPHP

On LiteSpeed, update the lsphp packages and restart the web server so the old lsphp processes exit. On FrankenPHP, update to a release built on the fixed PHP and restart it.

Why the restart matters

Updating the package replaces the files on disk, but PHP-FPM workers and Apache children already in memory keep running the old code until they are restarted. Patched files with unpatched processes still leave you exposed. After restarting, check the running version again.

Step 3: If you can't patch today

If a fixed package isn't available for your setup yet, or you need a maintenance window, these settings reduce exposure. Each covers only part of the problem and comes with side effects, so treat them as a bridge to patching.

They must be set at the administrator level so applications can't override them. Setting them with ini_set() or in a .user.ini file is too late to help.

PHP-FPM pool (for example, /etc/php/8.3/fpm/pool.d/www.conf):

php_admin_value[memory_limit] = -1
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 only:
php_admin_flag[fatal_error_backtraces] = off

Apache with mod_php (in the virtual host or server configuration):

php_admin_value memory_limit -1
php_admin_flag session.upload_progress.enabled off
# PHP 8.5 only:
php_admin_flag fatal_error_backtraces off

Then restart every PHP worker.

What each setting does and what it costs:

  • memory_limit = -1 removes the predictable out-of-memory point the known exploit relies on for CVE-2026-109618. The cost is that one runaway script can now use all of the server's memory. A container or cgroup memory limit can protect availability, but it doesn't make you safe, and PHP warns against using RLIMIT_AS or RLIMIT_DATA because they can trigger the vulnerable path themselves.

  • session.upload_progress.enabled = 0 closes the confirmed session path of CVE-2026-109619. Few sites use upload progress bars, so the cost is usually small.

  • fatal_error_backtraces = 0 (PHP 8.5 only) closes the confirmed backtrace path.

  • Request limits. PHP suggests post_max_size = 8M if you don't need uploads, or 32M if you do, with upload_max_filesize below that. At the reverse proxy or WAF, also cap request size and the number of multipart parts. If an application doesn't accept forms with files at all, block multipart requests before they reach PHP.

  • A WAF in front of the site, such as Cloudflare's emergency rules, helps only for traffic that actually passes through it. Anyone who can reach your server's IP address directly bypasses it.

Step 4: Check whether someone got in before you patched

Patching closes the hole. It doesn't tell you whether someone used it between October 17 and the moment you patched. Investigators recommend starting your review from October 17, and every advisory repeats the same warning: an absence of suspicious log entries does not prove a server is clean.

Places to look:

# PHP files created or modified since October 17
find /var/www /home -type f -name '*.php' -newermt '2026-10-17' -ls 2>/dev/null

# PHP files inside upload folders (there should normally be none)
find /var/www /home -path '*/uploads/*' -name '*.php' 2>/dev/null

# WordPress administrator accounts and when they were created
wp user list --role=administrator --fields=ID,user_login,user_registered

# Scheduled tasks for the web user and the system
sudo crontab -l -u www-data
ls -la /etc/cron.d /var/spool/cron

# SSH keys that shouldn't be there
sudo cat /root/.ssh/authorized_keys

# Repeated PHP worker crashes since October 17
journalctl -u php8.3-fpm --since '2026-10-17' | grep -iE 'signal|segfault'

# Unfamiliar processes holding network connections
sudo ss -tupn

Warning signs include admin users nobody created, PHP files with random names or encoded content, new cron entries, unknown SSH keys, unexpected outbound connections, and spam or redirects that appear only for search engines or mobile visitors. Attackers can change file dates, so a clean result from the find command is reassuring but not proof.

Step 5: Recovering a compromised site

If you found signs of compromise, or the site handles sensitive data and was unpatched for days, treat it as an incident. Order matters here, and the most common mistake is restoring before patching.

  1. Contain. Put the site into maintenance mode or block public traffic. If the server hosts other sites, assume they may be affected too.

  2. Preserve evidence. Take a snapshot or a full copy of the current state before you change anything. You'll need it to work out what happened and what data was exposed.

  3. Patch first. Update PHP and restart it before restoring anything. Restoring a clean site onto a vulnerable runtime puts it straight back in reach of the same attack.

  4. Restore from a known-good point. Use a backup taken before October 17. If you're not sure the server itself is clean (new system users, unknown SSH keys, modified binaries), rebuilding on a fresh server is safer than cleaning the old one.

  5. Reinstall code from official sources. Download fresh copies of the CMS core, plugins and themes rather than trusting files from the compromised server.

  6. Rotate every secret the application could read. That includes database passwords, API keys, payment gateway keys, SMTP credentials, WordPress salts in wp-config.php, Laravel's APP_KEY, and SSH keys. Force a password reset for admin users. CVE-2026-109619 can leak memory, so even values the attacker never wrote to disk may be exposed.

  7. Bring back legitimate changes made after the backup. Orders, form entries and posts created since October 17 need to be exported from the evidence copy and re-imported carefully, never by copying files back wholesale.

  8. Watch closely for a few weeks. Monitor for new admin users, file changes and outbound traffic. Attackers who kept a foothold often return.

  9. Check your notification duties. If personal data may have been accessed, data protection laws such as Egypt's Law 151 of 2020 or the GDPR may require you to notify a regulator or the affected users within a deadline. Ask your legal adviser early.

For the restore itself, see our step-by-step WordPress backup and restore guide.

Lessons worth keeping after the patch

  • Keep backups old enough. This incident had a four-day window before disclosure. What saves you is having a restore point from before the problem started, which depends on how long backups are kept, not just how often they are taken. A copy stored off the server matters too. Our guide to the 3-2-1 backup strategy for a VPS covers this.

  • Test a restore before you need one. A backup you have never restored is a hope, not a plan.

  • Keep an inventory of PHP versions. Knowing which servers run which version turns a stressful day into a short checklist.

  • Retire end-of-life PHP. Sites on PHP 7.3 or older have no official fix for this or any future bug.

  • Write down your recovery steps now, including who decides to take a site offline and who contacts customers. A standby server in a second location, covered in our disaster recovery guide, shortens the downtime even further.

  • Harden the server around PHP. Use SSH keys, a firewall and fail2ban, as described in our guide to securing a Linux VPS.

If you host with IM HOST

We updated PHP on IM HOST's hosting servers as soon as the fix was announced, and no IM HOST customer was affected. Here is what each service gives you if you ever need a restore or a hand:

  • Shared, WordPress and application hosting include free daily backups for the full length of your subscription. Restores are handled by our support team on request: tell us which site and the date you want to go back to, and we'll confirm the restore points available.

  • Cloud VPS includes a free automatic backup every 72 hours, for the full length of your subscription.

  • Linux VPS is unmanaged by default: you have full root access, so updating PHP and the operating system is in your hands. The steps above apply directly. Daily Backups and Managed Support are available as add-ons if you'd rather not handle this alone.

Need help checking a site, restoring a backup, or planning an upgrade away from an old PHP version? Contact IM HOST support.

Frequently asked questions

Is WordPress affected?

Yes. The bugs are in PHP itself, so any PHP application can be reached, including WordPress, WooCommerce, Drupal, Joomla, Magento and Laravel. Updating WordPress or its plugins doesn't fix the problem. You need to update PHP.

My site is behind Cloudflare. Do I still need to patch?

Yes. WAF rules cover known attack patterns and only for traffic that passes through Cloudflare. If your server's IP address can be reached directly, the protection is bypassed. Use the WAF to buy time, not as the fix.

My server shows PHP 8.3.6. Am I vulnerable?

Not necessarily. Distribution packages often keep an older version number and add security fixes to it. Check your distribution's security notice, or search the package changelog for CVE-2026-109618 and CVE-2026-109619.

Is setting memory_limit to -1 enough?

No. It reduces the risk from one of the two bugs, doesn't address the other, and lets a single script use all of the server's memory. It is a temporary measure until you patch.

I'm on PHP 7.4. What should I do?

PHP 7.4 to 8.1 only received minimal source backports, and versions before that receive nothing. The practical options are a distribution or long-term-support vendor that has issued a fixed package, or upgrading to PHP 8.2 or newer. If neither is possible right away, apply the temporary settings above and keep the site behind a WAF until you can upgrade.

I patched on October 21. Am I safe?

You're safe from new attempts through these bugs. Attempts were seen from October 17, so it's still worth running the checks in Step 4 for the days before you patched.

Sources

More from our blog

Discover more practical guides and product insights from the IM Host team.

View all articles