Feldnotizen


4 Min. Lesezeit

Welche meiner Container laufen mit PHP?

Am 21. Oktober 2026 hat PHP zwei kritische Lücken geschlossen, über die sich aus der Ferne Code ausführen lässt: CVE-2026-109618 und CVE-2026-109619, genannt «PHP2Shell» (Warnung, englisch). Behoben sind sie in 8.5.12, 8.4.27, 8.3.36 und 8.2.35. Alles Ältere auf diesen Zweigen ist betroffen, und 8.1 und älter bekommen gar keinen Fix.

Ich schreibe kein PHP. Das heisst aber nicht, dass ich keins betreibe. Viel Software, die ich hoste, kommt als Docker-Image, und manche dieser Images haben PHP drin, ohne dass es draufsteht. Die erste Frage war also nicht «Wie patche ich?», sondern «Wo läuft überhaupt PHP?».

Pebble, ein kleiner roter Stein, untersucht etwas mit einer Lupe, zwischen gestapelten Schiffscontainern.

Das Skript

Es geht alle laufenden Container durch, einmal pro Image, und sucht nach einem PHP-Binary: php, php-fpm, php8 und so weiter. Findet es eins, fragt es dieses Binary nach seiner Version.

Zwei Entscheidungen sorgen dafür, dass es mit jedem Image funktioniert:

  • Es findet das Binary mit docker export und tar -t, nicht mit which php im Container. Images auf Basis von scratch oder Distroless haben keine Shell, und alles, was eine braucht, scheitert dort.
  • Die Version liest es, indem es das Binary mit docker exec direkt aufruft. Auch dafür braucht es keine Shell.

Es läuft unter Linux und mit der Bash 3.2, die macOS immer noch mitliefert. Deshalb führt es die Liste der geprüften Images als String und nicht als assoziatives Array.

#!/usr/bin/env bash
# Check every running container's image for a PHP binary (php, php-fpm, php8, ...)
# and print the PHP version if one is found.
#
# Detection uses `docker export` (works even on images without a shell, e.g.
# scratch/distroless). The version is read by running the binary directly with
# `docker exec <container> <binary> -v`, which also needs no shell.

set -euo pipefail

COL=80
PATTERN='(s?bin)/php(-fpm)?[0-9.]*$'

# newline-separated list of images already checked (macOS ships bash 3.2,
# which has no associative arrays, so `declare -A` is not an option)
seen_list=""

printf "%-${COL}s %s\n" "IMAGE" "PHP?"
printf "%-${COL}s %s\n" "-----" "----"

containers=$(docker ps --format '{{.ID}} {{.Image}}')

while read -r id image; do
  [[ -z "$id" ]] && continue

  # one check per unique image
  if grep -qxF -- "$image" <<< "$seen_list"; then continue; fi
  seen_list="$seen_list"$'\n'"$image"

  matches=$(docker export "$id" 2>/dev/null | tar -t 2>/dev/null | grep -E "$PATTERN" || true)

  if [[ -z "$matches" ]]; then
    printf "%-${COL}s %s\n" "$image" "no"
    continue
  fi

  # prefer the CLI binary (php, php8, ...) over php-fpm for the version lookup
  bin=$(echo "$matches" | grep -vE 'php-fpm' | head -n1 || true)
  [[ -z "$bin" ]] && bin=$(echo "$matches" | head -n1)
  bin="${bin#./}"

  version=$(docker exec "$id" "/$bin" -v 2>/dev/null | grep -oE 'PHP [0-9]+\.[0-9]+\.[0-9]+[^ ]*' | head -n1 || true)
  [[ -z "$version" ]] && version="version unknown"

  printf "%-${COL}s %s\n" "$image" "YES  $version  (/$bin)"
done <<< "$containers"

Lade es als check_php_containers.sh herunter oder hol es direkt auf den Server:

curl -fsSL https://panzeri.rocks/files/check_php_containers.sh -o check_php_containers.sh
bash check_php_containers.sh

Lies ein Skript, bevor du es auf einem Server ausführst. Das gilt auch für dieses.

Dann patchen

Als ich wusste, welche Images PHP enthalten und in welcher Version, war der Fix langweilig. In jedem Compose-Projekt:

docker compose pull
docker compose up -d --force-recreate

Neue Images ziehen, dann jeden Container neu erstellen, damit keiner mehr mit dem alten läuft. Drei Minuten, und das Problem war gelöst.

Lass das Skript danach noch einmal laufen. Das Pullen hilft nur, wenn der Maintainer den Tag schon mit einem gefixten PHP neu gebaut hat. Zeigt ein Image immer noch eine Version unter dem Fix, muss es neu gebaut werden, vom Maintainer oder von dir.

Was es nicht kann

  • Es schaut nur laufende Container an. Ein gestoppter Container mit altem Image ist beim nächsten Start immer noch ein Risiko.
  • docker export schreibt das ganze Dateisystem eines Containers in die Pipe. Bei grossen Images dauert das jeweils ein paar Sekunden.
  • Es findet PHP-Binaries in bin- und sbin-Verzeichnissen. PHP, das in ein anderes Programm einkompiliert ist und kein eigenes php mitbringt, taucht nicht auf.

Warum das wichtiger ist als der Patch

Der Patch ging schnell, weil ich genau wusste, wo er hin musste. Der meiste Stress bei einer Sicherheitswarnung kommt daher, dass man nicht weiss, ob man betroffen ist. Container machen das schlimmer: Du wählst ein Image wegen der Software, und die Laufzeitumgebung kommt ungefragt mit. Ein Skript wie dieses macht aus «Bin ich betroffen?» eine Tabelle. Halte eins bereit für jede Laufzeitumgebung, die gern in den Schlagzeilen landet.

Mehr in Feldnotizen

Teilen