On 21 October 2026 the PHP project shipped an emergency release for two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell. Both are rated CVSS v4.0 9.2 and allow remote code execution without logging in. Details had leaked a day before the planned disclosure, so the patch window was short. This is a quick write-up of how I patched the WordPress stack that runs this blog.
What happened, in short
- CVE-2026-109618 (“RCE via OOM corruption”): a crafted multipart request can push PHP into an out-of-memory error at a moment when request data is only half initialized. Request cleanup then works on that broken state.
- CVE-2026-109619 (“RCE via stale global state”): state from one request can survive into the next request handled by the same worker, leading to a use-after-free. Session upload progress is one of the confirmed paths.
- Fixed versions: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP 8.1 and older get no official release.
- Most exposed: Apache with mod_php, where the OOM bug can fire before any application code runs. PHP-FPM is affected too, but the exploit paths are more conditional.
The important part: this is a bug in the PHP runtime. Updating WordPress, plugins or themes does nothing. You need a new PHP build. The PHP team also states that updating is the only complete fix; config tweaks such as disabling session.upload_progress only close some paths.
My setup
The blog runs as a Docker Compose stack: MariaDB, Redis, the official wordpress:php8.2-fpm image, nginx in front of it, a small cron container using the same WordPress image, and Adminer. TLS is terminated on a separate Nginx Proxy Manager host. While waiting for patched images, I simply disabled the proxy host, so nothing from the internet reached PHP.
Do I need to change the image tag?
No. wordpress:php8.2-fpm is a floating tag, and PHP 8.2 is still a supported branch, so the tag gets rebuilt with 8.2.35. A pull is enough. (Pinned tags with an old PHP like php8.1 are a different story: switch to a supported branch.)
The fix
First pull, then check the PHP version inside the new image before touching the running containers:
docker compose pull php cron adminer
docker run --rm --entrypoint php wordpress:php8.2-fpm -r 'echo PHP_VERSION, PHP_EOL;'
# 8.2.35
Only once that shows a fixed version, recreate the containers and confirm the running one picked it up:
docker compose up -d php cron
docker compose exec php php -v
# PHP 8.2.35 (cli) (built: Oct 21 2026 16:03:01) (NTS)
Remember: docker restart patches nothing. The container has to be recreated from the new image.
Don’t forget the other PHP containers
Two things in the same stack were easy to overlook:
- The cron container uses the same WordPress image, so it needs recreating as well.
- Adminer is PHP too. The official
adminerimage runs on the official PHP image. After the pull it was on PHP 8.4.27, so it was fine once recreated. It was also a good reminder that a database admin UI should be bound to localhost or a VPN address, not0.0.0.0.
docker run --rm --entrypoint php adminer:latest -r 'echo PHP_VERSION, PHP_EOL;'
docker compose up -d adminer
Quick compromise check before going back online
Exploitation attempts were reported shortly after the leak, so before re-enabling the site I looked for PHP files written since then:
find ./wordpress -name '*.php' -newermt '2026-10-20' -not -path '*/cache/*'
Legitimate hits from plugin updates are possible. A .php file in wp-content/uploads/ is not: that folder should never contain PHP.
Next step: PHP 8.2 end of life
PHP 8.2 reaches end of life on 31 December 2026. Moving to wordpress:php8.4-fpm is on my list, but as a separate change with a plugin and theme test, not in the middle of an emergency patch.