Stopgap fix: patch CVE-2026-109618 / 109619 (PHP RCE) while still on PHP 7.4
On 2026-10-21 the PHP project disclosed two unauthenticated remote code execution vulnerabilities (php.net announcement): CVE-2026-109618 (GHSA-w8hw-w9fx-j79w): out-of-memory handling can interrupt PHP while internal
On 2026-10-21 the PHP project disclosed two unauthenticated remote code execution vulnerabilities (php.net announcement):
- CVE-2026-109618 (GHSA-w8hw-w9fx-j79w): out-of-memory handling can interrupt PHP while internal request data is only partly initialized, and request cleanup may then treat that invalid data as valid.
- CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp): internal state owned by a request can survive after its memory is freed, and a later request on the same worker may use it.
Every PHP installation that serves web requests is affected: PHP-FPM, Apache mod_php, php-cgi and others. The advisory rates the risk highest for mod_php, where an unauthenticated request can trigger the attack before any application code runs.
Fixed releases exist only for PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12. PHP 7.4 is end of life, but because the bugs are so severe, the PHP security team published minimal source backports for 7.4, 8.0 and 8.1.
The official Docker images php:7.4-* will not be rebuilt. If your Dockerfile says FROM php:7.4-apache, you stay vulnerable unless you act.
So I rebuilt those images with the backport applied and published them as open source:
👉 https://github.com/softjapan/php74-cve-patched
⚠️ This is a stopgap, not a fix for PHP 7.4.
- Only CVE-2026-109618 and CVE-2026-109619 are fixed. Other issues found after 7.4.33 are not.
- The base OS, Debian 11 (bullseye), reached end of LTS in 2026-08. OS packages such as OpenSSL and Apache receive no more updates.
- These are not official images from the PHP project or Docker.
The real fix is upgrading to PHP 8.2 or newer. Use these images to buy time for that upgrade.
TL;DR
- FROM php:7.4-apache
+ FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache
- Change one
FROMline (apache / fpm / cli, amd64 / arm64). - Rebuild, redeploy and restart every PHP worker.
- Confirm the fix with
check-patched.sh. - Plan your upgrade to PHP 8.2+. You are done only after this step.
Step 1: check whether you are vulnerable
The catch: after the backport, php -v still prints 7.4.33. The version number cannot tell you whether you are protected.
This script looks inside the PHP binaries instead:
curl -fsSLO https://raw.githubusercontent.com/softjapan/php74-cve-patched/main/scripts/check-patched.sh
sh check-patched.sh php:7.4-apache # an image
sh check-patched.sh --container my-web-1 # a running container
sh check-patched.sh --local # this machine (VM / bare metal)
The official image:
Image: php:7.4-apache
PHP version: 7.4.33
VULNERABLE /usr/lib/apache2/modules/libphp7.so
VULNERABLE /usr/local/bin/php
RESULT: VULNERABLE to CVE-2026-109618 / CVE-2026-109619
The patched image:
Image: ghcr.io/softjapan/php74-cve-patched:7.4-apache
PHP version: 7.4.33
patched /usr/lib/apache2/modules/libphp7.so
patched /usr/local/bin/php
RESULT: PATCHED (backport found in every PHP binary)
- PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 and newer count as fixed. Older versions are checked for
zend_new_array_singleton, a function the backport adds. - It checks each SAPI binary separately: the CLI, plus
libphp7.so(mod_php) andphp-fpm, which are what actually serve your requests. That catches a patched CLI next to an unpatched Apache module. - Exit code
0means patched and1means vulnerable, so you can block vulnerable images in CI.
The check is a heuristic. A backport from a distribution or LTS vendor written a different way may show as vulnerable. In that case, check with your vendor.
Step 2: switch images
The same images, with the same tags and digests, are on two registries:
| Official image | GHCR | Docker Hub |
|---|---|---|
php:7.4-apache |
ghcr.io/softjapan/php74-cve-patched:7.4-apache |
softjpn/php74-cve-patched:7.4-apache |
php:7.4-fpm |
ghcr.io/softjapan/php74-cve-patched:7.4-fpm |
softjpn/php74-cve-patched:7.4-fpm |
php:7.4-cli |
ghcr.io/softjapan/php74-cve-patched:7.4-cli |
softjpn/php74-cve-patched:7.4-cli |
They work exactly like the official images:
-
docker-php-ext-install,docker-php-ext-configure,peclandphp.ini-productionall work as before. - CI checks that
php -mlists the same extensions as the official image. -
apt-get installworks, because apt now points atarchive.debian.org.
Example for Laravel:
FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache
RUN apt-get update && apt-get install -y --no-install-recommends \
libzip-dev libpng-dev libjpeg-dev libfreetype6-dev \
&& rm -rf /var/lib/apt/lists/* \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" gd pdo_mysql zip bcmath opcache \
&& a2enmod rewrite
Changing the image is not enough on its own. Containers and workers that are already running keep the old binaries. Rebuild, redeploy and restart every PHP worker, then run
check-patched.sh --container <name>against the running container.
What exactly changed
Very little. The images start from the last PHP 7.4 Dockerfiles in docker-library/php (commit 1361ba95) and change three things:
-
apt uses
archive.debian.org. Bullseye has left the regular mirrors, soapt-get updatethere returns 404. -
The backport is applied to the PHP 7.4.33 source with
patch -p1 --fuzz=0. If it does not apply cleanly, the build fails. -
The build checks the result. If
php,php-fpmorlibphp7.sois missing the fix, the build fails.
The patch is byte-identical to the upstream commit php/php-src@712e60f. On every push and every week, CI checks two things:
- The vendored patch still matches the upstream commit.
- The upstream backport branch has no new commits. If it does, CI fails so a new patch can be shipped.
My first internal version re-ran ./configure on top of php:7.4-apache. That approach hard-coded x86_64 options and let the fpm and cli builds drift from each other. Starting from the official Dockerfiles keeps the images compatible with the official ones and makes multi-arch builds straightforward.
Trust, but verify
You probably should not pull a security image from a stranger on faith, so everything here can be checked:
- Images are built only by GitHub Actions in the repository, with SBOM and provenance attestations.
- Images are signed with cosign (keyless):
cosign verify ghcr.io/softjapan/php74-cve-patched:7.4-apache \
--certificate-identity-regexp '^https://github.com/softjapan/php74-cve-patched/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
- Pin a digest (
@sha256:...) in production. - Or skip these images and build them yourself. The guide also covers applying the patch with
patch -p1if you build PHP from source without Docker.
Mitigations if you cannot switch right away
The advisory also lists mitigations. It is clear that they only block some attack paths and do not make an installation safe, but they are useful as a bridge until you switch, and as defense in depth afterwards.
CVE-2026-109618 (OOM corruption)
- Set
memory_limit=-1in a way applications cannot override, such asphp_admin_valuein the FPM pool or Apache config. Check that no other setting brings back a finite limit.- Use a container or cgroup memory limit instead, ideally one that kills only the affected worker.
- Do not use
RLIMIT_ASorRLIMIT_DATAas a substitute. They can cause the very allocation failure you are trying to avoid.
- Lower
post_max_size:8Mif you have no file uploads,32Mif you do. Keepupload_max_filesizebelowpost_max_size. - Also limit request size, field count, multipart part count, header count and nesting depth at the web server or proxy, including for requests without a usable
Content-Length.
CVE-2026-109619 (stale global state)
- Set
session.upload_progress.enabled=0and restart every worker.- Set it in
php.ini, the FPM pool or the server config, which apply before request parsing.ini_set()and.user.inido not help.
- Set it in
- Remove invalid or deprecated settings applied per request that emit diagnostics before initialization. Hiding the diagnostics with
@ordisplay_errorsdoes not help. - Fix fatal errors and timeouts that an attacker can trigger.
The images ship an optional file with some of these settings (session.upload_progress.enabled = 0 and lower post_max_size / upload_max_filesize). It is disabled by default, so PHP behaves exactly like the official image. To enable it:
RUN cp /usr/local/share/php74-cve-patched/zz-cve-2026-mitigations.ini "$PHP_INI_DIR/conf.d/"
After the stopgap
One more time: this stops the bleeding. It does not cure the patient.
- [ ] Find every PHP 7.4 environment and run
check-patched.shon it. - [ ] Switch images, redeploy and restart the workers.
- [ ] Confirm
check-patched.sh --containerreportsPATCHEDon the running containers. - [ ] Add the mitigation settings where they make sense.
- [ ] Set a plan and a deadline for moving to PHP 8.2+. PHP 8.2 itself reaches end of life on 2026-12-31, so consider 8.3 or newer.
- [ ] Tell your team that the OS (Debian 11) is not getting updates either.
Summary
- CVE-2026-109618 and CVE-2026-109619 are unauthenticated RCEs. Treat mod_php as top priority.
- The official PHP 7.4 Docker images will not be fixed. If you use
php:7.4-*, you stay vulnerable. -
php74-cve-patched is a stopgap that brings in the official backport with a one-line
FROMchange. - The real fix is upgrading to PHP 8.2 or newer.
PHP 8.0 / 8.1 images (official backports exist for those too) and Alpine variants are on the roadmap. Open an issue if you need one. And if this saved you an emergency upgrade, a ⭐ on GitHub helps other people stuck on PHP 7.4 find it.
👉 https://github.com/softjapan/php74-cve-patched
References
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.