You can patch PHP2Shell (CVE-2026-109619 and CVE-2026-109618) on Amazon Linux 2023 with one dnf command, but only if you tell dnf which repository release to read. AL2023 locks every instance to the repository version its AMI was built from, so a plain dnf upgrade on an older instance reports nothing to install while the fixed php8.x packages sit in a newer release.
This guide shows you how to check whether your instance is affected, read the ALAS2023 advisory from dnf, move to the fixed release and prove that PHP-FPM serves the new build. I last ran every step in October 2026 on an Amazon Linux 2023 guest booted from the 2023.12.20261016 image with php8.4 behind nginx, so the before and after output below comes from that box.
Check the PHP2Shell fixed versions for Amazon Linux 2023
Compare the package on your server with the build in this table. Amazon Linux 2023 and Debian move to the new upstream version, while Ubuntu keeps the old version number and changes only the package release, so on Ubuntu you compare the full string:
| Release | Package | Fixed build | Advisory |
|---|---|---|---|
| Amazon Linux 2023 | php8.5 | 8.5.12-1.amzn2023.0.1 | ALAS2023-2026-3304 |
| Amazon Linux 2023 | php8.4 | 8.4.27-1.amzn2023.0.1 | ALAS2023-2026-3303 |
| Amazon Linux 2023 | php8.3 | 8.3.36-1.amzn2023.0.1 | ALAS2023-2026-3302 |
| Amazon Linux 2023 | php8.2 | 8.2.35-1.amzn2023.0.1 | ALAS2023-2026-3301 |
| Amazon Linux 2023 | php8.1 | No fix planned | ALAS CVE page |
| Ubuntu 26.10 | php8.5 | 8.5.9-0ubuntu2.1 | USN-8948-1 |
| Ubuntu 26.04 LTS | php8.5 | 8.5.4-0ubuntu1.5 | USN-8948-1 |
| Ubuntu 24.04 LTS | php8.3 | 8.3.6-0ubuntu0.24.04.13 | USN-8948-1 |
| Ubuntu 22.04 LTS | php8.1 | 8.1.2-1ubuntu2.28 | USN-8948-1 |
| Ubuntu 20.04 (Pro) | php7.4 | 7.4.3-4ubuntu2.29+esm5 | USN-8948-2 |
| Ubuntu 18.04 (Pro) | php7.2 | 7.2.24-0ubuntu0.18.04.17+esm14 | USN-8948-2 |
| Ubuntu 16.04 (Pro) | php7.0 | 7.0.33-0ubuntu0.16.04.16+esm22 | USN-8948-2 |
| Ubuntu 14.04 (Pro) | php5 | 5.5.9+dfsg-1ubuntu4.29+esm18 | USN-8948-2 |
| Debian 14 (forky, testing) | php8.4 | 8.4.27-1 | Debian tracker |
| Debian 13 (trixie) | php8.4 | 8.4.27-1~deb13u1 | DSA-6574-1 |
| Debian 12 (bookworm) | php8.2 | 8.2.35-1~deb12u1 | DLA-4848-1 |
| RHEL, Rocky, AlmaLinux 10 | php, php8.4 | php-8.3.36-1.el10_2, php8.4-8.4.27-1.el10_2 | RHEL guide |
| RHEL, Rocky, AlmaLinux 9 / 8 | php streams | 9: php:8.3 8.3.36, php:8.2 8.2.35, default php 8.0.30-8.el9_8.1; 8: php:8.2 8.2.35, php:7.4 7.4.33-8 | RHEL guide |
| php.net upstream | source | 8.5.12, 8.4.27, 8.3.36, 8.2.35 | GHSA-6wmf-4rv5-p3rp |
You get all four AL2023 builds from repository release 2023.12.20261021 and every later release. The Ubuntu 14.04 to 20.04 rows come from the esm-infra pocket, so you only receive them with Ubuntu Pro attached. On Ubuntu and Debian, follow the step-by-step PHP2Shell guide for Ubuntu or the Debian 13 and 12 patch steps. RHEL, Rocky Linux and AlmaLinux have their own AppStream module and Remi details in the RHEL, Rocky and AlmaLinux PHP2Shell guide.
Read the risk before you schedule it
PHP2Shell is two remote code execution bugs in the PHP engine itself, so every application on an affected build is exposed, whatever framework or CMS it runs. Your own code does not need a bug for you to be at risk. Each CVE breaks a different part of the request lifecycle:
| CVE | What goes wrong | Confirmed trigger |
|---|---|---|
| CVE-2026-109618 | PHP aborts an out-of-memory request with longjmp() and leaves request memory uninitialized for the cleanup code that reads it next | Multipart request parsing |
| CVE-2026-109619 | Global state keeps pointing at request memory after it is freed, so a later request in the same worker reads stale data | Session upload progress; fatal error backtraces on 8.5 |
Amazon rates the AL2023 advisories Important rather than Critical, because the attack needs a precise memory condition (CVSS v3.1 8.1, attack complexity high). The PHP CNA scores CVE-2026-109619 higher, at CVSS v4 9.2. Do not read the lower label as permission to wait. FIRST puts the EPSS score for both CVEs at 0.99999. HKCERT’s bulletin, last updated 27 October 2026, reports both exploited in the wild, and CISA added both to its Known Exploited Vulnerabilities catalog on 26 October 2026. With that record, you should treat any internet-facing PHP instance as a same-day job. The PHP2Shell overview and checker covers the mechanism and every platform in one place.
Before you begin
You need shell access to the instance and a short window, because PHP-FPM restarts during the upgrade and a full release upgrade also brings a new kernel. Check that you have these:
- An EC2 instance or VM running Amazon Linux 2023, reachable over SSH or Session Manager, with a user that can run
sudo. - Outbound HTTPS to the Amazon Linux repositories, or a VPC endpoint or mirror that serves release 2023.12.20261021 or later.
- A snapshot or AMI of the instance if it is not rebuilt from code, so that you can roll back a full release upgrade.
- A maintenance window long enough for a PHP-FPM restart, plus one reboot if you take the full release upgrade.
The lab instance in this guide is a stock AL2023 KVM image running nginx and PHP-FPM, installed the same way as in the PHP 8.4 on Amazon Linux 2023 install guide. If you want a disposable copy to rehearse on first, boot the same image locally with the AL2023 qcow2 on KVM steps.
Find the PHP stream on the instance
Amazon Linux 2023 ships PHP as parallel streams named php8.1 to php8.5, and only one of them is usually installed. List the common package of every stream on the box together with the repository release the instance is locked to:
rpm -qa 'php8*-common'
rpm -q system-release
On the lab instance rpm reported the php8.4 stream at 8.4.26 and a release lock of 2023.12.20261016, which is older than the fix:
php8.4-common-8.4.26-1.amzn2023.0.1.x86_64
system-release-2023.12.20261016-0.amzn2023.noarch
Read your version against the table above. You need both lines of output to judge it. The instance is affected if it runs any of these:
- php8.2 below 8.2.35
- php8.3 below 8.3.36
- php8.4 below 8.4.27
- php8.5 below 8.5.12
- php8.1 at any version
A system-release older than 2023.12.20261021 tells you that your instance cannot even see the fixed packages yet, so the next check will look clean whatever PHP version you run.
If you see php8.1, plan a stream change instead of a patch. Amazon lists php8.1 on AL2023 as “No Fix Planned” for CVE-2026-109619, and the newest php8.1-cli build in the latest repository release is still 8.1.34-1.amzn2023.0.1, the same build the 16 October release carried. Move the application to php8.3 or php8.4, or read the PHP2Shell options for end-of-life PHP 8.1 if the code cannot move this week.
Check whether dnf sees the fix
Most AL2023 patch audits go wrong at this step, so you should see the failure once for yourself. Ask dnf for PHP updates the usual way first:
sudo dnf check-update 'php8.4*'; echo exit=$?
dnf returns exit code 0, which means no updates, and lists no php8.4 package at all. All you get is a banner about newer releases:
Last metadata expiration check: 0:00:41 ago on Thu Oct 29 01:45:07 2026.
================================================================================
WARNING:
A newer release of "Amazon Linux" is available.
Available Versions:
Version 2023.12.20261021:
Run the following command to upgrade to 2023.12.20261021:
dnf upgrade --releasever=2023.12.20261021
Release notes:
https://docs.aws.amazon.com/linux/al2023/release-notes/relnotes-2023.12.20261021.html
Version 2023.12.20261026:
Run the following command to upgrade to 2023.12.20261026:
dnf upgrade --releasever=2023.12.20261026
Release notes:
https://docs.aws.amazon.com/linux/al2023/release-notes/relnotes-2023.12.20261026.html
================================================================================
exit=0
That exit code is how vulnerable instances pass a patch audit. AL2023 uses deterministic upgrades, and new security updates only appear in new repository versions, so dnf compares your packages with the locked 2023.12.20261016 repository and finds nothing newer. If your monitoring script only parses dnf check-update, it reports this box as current. To see the advisory that applies to your stream, point dnf at the latest release instead:
sudo dnf updateinfo list --releasever=latest --cve CVE-2026-109619
With the release override, dnf names ALAS2023-2026-3303 and the three php8.4 packages it replaces:
Last metadata expiration check: 0:00:32 ago on Thu Oct 29 01:46:02 2026.
ALAS2023-2026-3303 Important/Sec. php8.4-cli-8.4.27-1.amzn2023.0.1.x86_64
ALAS2023-2026-3303 Important/Sec. php8.4-common-8.4.27-1.amzn2023.0.1.x86_64
ALAS2023-2026-3303 Important/Sec. php8.4-fpm-8.4.27-1.amzn2023.0.1.x86_64
You only see the advisory for the stream you have installed, because dnf matches advisories to installed package names. Amazon published ALAS2023-2026-3301 for php8.2, 3302 for php8.3 and 3304 for php8.5, so on those streams the same query names that advisory instead. To read the full text with both CVE descriptions and the severity, run sudo dnf updateinfo info --releasever=latest ALAS2023-2026-3303 with your own advisory ID, or open the ALAS2023-2026-3303 page.
In the capture below you can see the three checks side by side on the unpatched instance: the old packages, the silent check-update and the advisory that only shows up against the latest release.

If your query against --releasever=latest returns nothing, you have three possible causes: the instance is already patched, no PHP stream is installed, or it runs php8.1, which has no advisory to match because Amazon plans no fix. Your first rpm check tells you which one it is, and the --all query in the verification section confirms a patched box, because an installed fix shows up there with an i in front. An empty result on php8.1 never means clean.
Install the PHP2Shell patch on Amazon Linux 2023
You have two ways to install the fixed build, and they differ in what else changes on the instance. AWS recommends taking a whole release rather than single packages, and that is the right default for anything you can reboot. Choose one:
- To patch PHP and everything else, upgrade to the newest release number from the banner. On the lab instance that was 2023.12.20261026: 2 new packages, 67 upgrades, a 78 MB download and a new kernel, so you reboot afterwards.
- To touch only PHP during the day, upgrade the php packages against
--releasever=latest. That was 3 packages and 6.7 MB on the lab box, with no reboot. The release lock stays where it was, so you still owe the instance a full release upgrade later.
Upgrade to the fixed release
Pin the release number instead of latest when you patch a fleet, so every instance lands on the same package set even if Amazon publishes another release halfway through your rollout:
sudo dnf upgrade --releasever=2023.12.20261026 -y
The full transaction is long, so the lab run below is filtered down to the summary, the kernel package and the result lines. The lab box had already taken the PHP-only path below, which is why no php8.4 line appears; on an unpatched instance the fixed php8.x packages are part of this same transaction:
Installing:
Transaction Summary
Install 2 Packages
Upgrade 67 Packages
Total download size: 78 M
(4/69): kernel-6.1.189-236.399.amzn2023.x86_64. 15 MB/s | 31 MB 00:02
Total 10 MB/s | 78 MB 00:07
Upgraded:
Installed:
Complete!
Unlike the PHP-only path, this one moves the lock. After the upgrade rpm -q system-release reported system-release-2023.12.20261026-0.amzn2023.noarch, and every later dnf run reads that release by default. Because a new kernel landed, sudo dnf needs-restarting -r printed “Reboot is required to fully utilize these updates”, so reboot the instance in your window:
sudo systemctl reboot
Your release number may differ from the one in that command, because 2023.12.20261026 was simply the newest release when this guide was tested. Run sudo dnf check-release-update first and use the newest version it lists. Anything from 2023.12.20261021 onward includes the PHP fix, so you never need to pick an older one.
Upgrade only the PHP packages
Use this path when a reboot has to wait for a later window. Replace php8.4 with your stream from the first check:
sudo dnf upgrade --releasever=latest -y 'php8.4*'
dnf pulls exactly the three packages named in the advisory and finishes in a few seconds:
Upgraded:
php8.4-cli-8.4.27-1.amzn2023.0.1.x86_64
php8.4-common-8.4.27-1.amzn2023.0.1.x86_64
php8.4-fpm-8.4.27-1.amzn2023.0.1.x86_64
Complete!
Afterwards the lab instance still reported system-release-2023.12.20261016, and dnf check-update still printed the newer-release banner. You should expect that, because a package override does not move the release lock. Your PHP fix is in place, but the rest of the operating system still misses every other fix since 16 October. Schedule the full upgrade before you close the ticket.
Restart PHP-FPM and verify the patch
A patched package on disk does not protect you until every PHP worker runs the new code. On AL2023 the php-fpm package restarts the service for you: the php8.4-fpm package carries a file trigger on /etc/php-fpm.d and /etc/php.d that runs systemctl try-restart php-fpm.service. The optional smart-restart package, which also restarts services after dnf runs, was not installed on the lab box, so the trigger alone did it. You can see the restart in the unit’s journal:
sudo journalctl -u php-fpm --no-pager | tail -12
In the lab journal, the stop and start at 01:47:53 is the upgrade itself, and the one at 01:48:53 is a manual restart a minute later:
Oct 29 01:45:22 app-node-1 systemd[1]: Starting php-fpm.service - The PHP FastCGI Process Manager...
Oct 29 01:45:22 app-node-1 systemd[1]: Started php-fpm.service - The PHP FastCGI Process Manager.
Oct 29 01:47:53 app-node-1 systemd[1]: Stopping php-fpm.service - The PHP FastCGI Process Manager...
Oct 29 01:47:53 app-node-1 systemd[1]: php-fpm.service: Deactivated successfully.
Oct 29 01:47:53 app-node-1 systemd[1]: Stopped php-fpm.service - The PHP FastCGI Process Manager.
Oct 29 01:47:53 app-node-1 systemd[1]: Starting php-fpm.service - The PHP FastCGI Process Manager...
Oct 29 01:47:53 app-node-1 systemd[1]: Started php-fpm.service - The PHP FastCGI Process Manager.
Oct 29 01:48:53 app-node-1 systemd[1]: Stopping php-fpm.service - The PHP FastCGI Process Manager...
Oct 29 01:48:53 app-node-1 systemd[1]: php-fpm.service: Deactivated successfully.
Oct 29 01:48:53 app-node-1 systemd[1]: Stopped php-fpm.service - The PHP FastCGI Process Manager.
Oct 29 01:48:53 app-node-1 systemd[1]: Starting php-fpm.service - The PHP FastCGI Process Manager...
Oct 29 01:48:53 app-node-1 systemd[1]: Started php-fpm.service - The PHP FastCGI Process Manager.
Restart it yourself anyway. The trigger uses try-restart, which does nothing if the service was stopped or failed during the transaction, so the package output alone cannot prove the new build is live. A manual restart costs you a second. If Apache httpd serves PHP on the same instance, restart httpd as well:
sudo systemctl restart php-fpm
systemctl is-active php-fpm
Now check what the web server actually runs, not only what the CLI reports. The CLI and FPM come from separate packages, so a test page served through nginx is the proof that matters. Create a temporary page that prints the version, the SAPI and the upload progress setting. The path below is the stock nginx document root on AL2023, which answers on localhost; if you replaced the stock server block, put the file in your site’s root instead:
echo '<?php echo PHP_VERSION, " ", php_sapi_name(), " upload_progress=", ini_get("session.upload_progress.enabled"), "\n";' | sudo tee /usr/share/nginx/html/v.php
Request it through the web server and compare the result with the CLI and the package database:
curl -s http://localhost/v.php
php -v | head -1
rpm -q php8.4-cli php8.4-fpm php8.4-common
All three must agree on 8.4.27, and the page must say fpm-fcgi. Before the patch the same page returned 8.4.26 fpm-fcgi upload_progress=1:
8.4.27 fpm-fcgi upload_progress=1
PHP 8.4.27 (cli) (built: Oct 19 2026 14:38:59) (NTS gcc x86_64)
php8.4-cli-8.4.27-1.amzn2023.0.1.x86_64
php8.4-fpm-8.4.27-1.amzn2023.0.1.x86_64
php8.4-common-8.4.27-1.amzn2023.0.1.x86_64
Do not look for the CVE numbers in the RPM changelog. The 8.4.27 entry on AL2023 reads only “Package updated to 8.4.27”, and the CVE IDs live in the ALAS advisory. On this distribution the version number is the evidence, which is simpler than on Ubuntu, where you have to match the package release.
Finally, confirm that dnf agrees nothing is pending. Adding --all marks ALAS2023-2026-3303 with an i for installed. Keep --releasever=latest in the query, because after the PHP-only path your instance is still locked to a release whose metadata has no such advisory, and the query would print nothing at all:
sudo dnf updateinfo list --releasever=latest --all --cve CVE-2026-109619
Every line starts with i, so the fix for that CVE is on the box:
i ALAS2023-2026-3303 Important/Sec. php8.4-cli-8.4.27-1.amzn2023.0.1.x86_64
i ALAS2023-2026-3303 Important/Sec. php8.4-common-8.4.27-1.amzn2023.0.1.x86_64
i ALAS2023-2026-3303 Important/Sec. php8.4-fpm-8.4.27-1.amzn2023.0.1.x86_64
Your end state should match this capture of the same instance after the release upgrade and the reboot, with the new kernel, the new release lock and FPM serving 8.4.27:

You see upload_progress=0 in that capture because the hardening from the next section was already in place on the lab box. On a fresh patch your page shows 1 until you change it, and the patch itself does not touch that setting.
Set partial mitigations if you cannot patch yet
No configuration change makes an unpatched PHP safe. The PHP security advisory says that updating to a release with the complete state reset patch is the only reliable mitigation and that there is no complete generic configuration mitigation. What you can do is close the confirmed paths and buy hours, not weeks:
- To close the confirmed session path of CVE-2026-109619, set
session.upload_progress.enabled = 0. The advisory calls this partial, because it does not cover the other request lifecycle paths, and suggests keeping it off after you patch too. - To close the backtrace path on php8.5, set
fatal_error_backtraces = 0. - To limit CVE-2026-109618, its advisory names
memory_limit=-1, enforced by the administrator in every pool, as the primary configuration mitigation. It still calls it incomplete, because PHP can reach the vulnerable out-of-memory path whenmmap()fails. With no per-request limit, one request can also consume all of the host’s memory. Set it withphp_admin_value[memory_limit] = -1, restart every worker, and confirm that no pool, directory or application setting restores a finite limit. The advisory also warns againstRLIMIT_ASandRLIMIT_DATA(systemdLimitAS=andLimitDATA=), since they makemmap()fail and trigger the bug, so do not reach for those as a memory cap. - To shrink the request bodies PHP parses, keep
post_max_sizeconservative and enforce the request size at nginx or Apache as well. The advisory warns that a multipart request without a usable declared length can pass thepost_max_sizecheck, so the web server limit is the one that holds. If no endpoint on the site acceptsmultipart/form-data, block it at the front end. - To stop a poisoned worker from serving the next request, the CVE-2026-109619 advisory offers
pm.max_requests = 1in the FPM pool for sites where performance is not a factor, because it costs a large share of throughput.
On AL2023 a drop-in file under /etc/php.d applies to both the CLI and FPM. Only the upload progress setting was applied and checked on the lab instance, because it is the one with almost no cost for most sites. Write the file:
echo "session.upload_progress.enabled = 0" | sudo tee /etc/php.d/99-php2shell.ini
sudo systemctl restart php-fpm
FPM reads ini files only when it starts, so the restart in that command is what applies the setting. Check the value through the web server afterwards, because a value that only the CLI sees protects nothing:
curl -s http://localhost/v.php
php --ri session | grep upload_progress.enabled
Both report the setting off, so FPM picked up the drop-in:
8.4.27 fpm-fcgi upload_progress=0
session.upload_progress.enabled => Off => Off
Pool files under /etc/php-fpm.d can override that value with php_admin_flag, php_flag or php_value, so grep them if the page still says 1. The web server side, with multipart limits for nginx and Apache and a Cloudflare origin lockdown, is covered in the PHP2Shell mitigation guide. The patched build also brings a new memory_hard_limit_percent directive (php -i on the lab box’s 8.4.27 lists it with no value set), which the memory_hard_limit_percent explainer covers before you tune it.
Update your AMIs and patch audits
Patching the running instance is half the job if an Auto Scaling group or a launch template still points at an AMI built before 2023.12.20261021. Every new instance you launch from that AMI boots locked to the old release, with the vulnerable PHP build, and your next scale-out event puts it back in service. Do these before you close the incident:
- To stop new instances from booting vulnerable, rebuild your golden AMI on release 2023.12.20261021 or later and update the launch template version.
- To catch instances that patch scripts miss, query
rpm -q system-releaseacross the fleet and flag anything older than 2023.12.20261021. - To keep audits honest, change any check that relies on
dnf check-updateto run with--releasever=latest, or to calldnf check-release-updateas well.
Your audit change matters beyond PHP2Shell. Every future AL2023 security fix arrives in a new release in the same way, so an audit that ignores the release lock will keep reporting your old instances as clean long after this incident is closed.
Remove the test page
Anyone who requests v.php learns your exact PHP version and SAPI, which is the first thing an attacker scanning for PHP2Shell wants to know. Delete it as soon as you have your result:
sudo rm -f /usr/share/nginx/html/v.php
Keep /etc/php.d/99-php2shell.ini unless an application needs upload progress bars, since the advisory recommends leaving that setting off after you patch. If you took the PHP-only path, put the full dnf upgrade --releasever run and its reboot on the calendar now, because until then the rest of the instance is still on the old release.