PHP2Shell: patching my Dockerized WordPress in five minutes

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 adminer image 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, not 0.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.

Sources

Leave a comment