← Alle Beiträge
Sicherheit und Betrieb//13 Min. Lesezeit

phpIPAM sicher aktualisieren: Warum das neueste Docker-Image nicht genügte

Warum die neuesten phpIPAM-Images den dringenden PHP-Fix noch nicht enthielten und wie wir PHP 8.3.36 mit Alpine-Paketen, eigenen Images, Kaltbackup und geprüfter Entra-Anmeldung bereitstellten.

Ein aktuelles Container-Image und eine korrigierte PHP-Laufzeit waren bei unserem phpIPAM-Update zwei verschiedene Dinge. Wir luden die neuesten Web- und Cron-Images herunter. Beide waren neuer als unsere laufenden Container. Trotzdem fehlte darin noch das gerade veröffentlichte PHP-Sicherheitsupdate.

Am 21. Oktober 2026 haben wir deshalb genauer hingeschaut: Welche PHP-Version läuft tatsächlich? Welche Version steckt im frisch geladenen Image? Und können wir die bereits verfügbaren Distributionspakete verwenden, ohne die funktionierende Anmeldung oder unsere Daten zu verlieren? Dieser Beitrag beschreibt unseren Weg bis zur bestätigten Aktualisierung. Containernamen in den Befehlen sind Beispiele und müssen an die eigene Installation angepasst werden.

Die Sicherheitsmeldung betraf PHP selbst

Das PHP-Projekt veröffentlichte an diesem Tag Korrekturen für CVE-2026-109618 und CVE-2026-109619. Die offizielle Sicherheitsmeldung nennt mögliche Codeausführung ohne vorherige Anmeldung und fordert zum Aktualisieren auf. Die korrigierten Versionen zum Veröffentlichungszeitpunkt waren PHP 8.2.35, 8.3.36, 8.4.27 und 8.5.12. Bei Distributionspaketen können auch ausdrücklich bestätigte Rückportierungen relevant sein.

Unser Fall betrifft den 8.3-Zweig. Die heute genannten Versionsnummern sind der historische Stand dieses Vorfalls. Wer den Artikel später verwendet, sollte die dann aktuellen Sicherheitsmeldungen und unterstützten PHP-Versionen prüfen.

phpIPAM ist eine PHP-Anwendung. Ein aktueller Stand der Anwendung allein konnte die Lücken in der Laufzeit daher nicht beheben. Die Sicherheitsmeldung betrifft unter anderem Apache mit mod_php. Auch bei einer Anmeldung über Microsoft Entra ID mussten wir deshalb den PHP-Stand prüfen.

Was die Entra Vorauthentifizierung schützt

Eine vorgeschaltete Microsoft-Entra-Vorauthentifizierung im Application Proxy kann den Zugang zu einem PHP-Backend begrenzen: Anfragen werden über diesen Weg erst nach Authentifizierung und Zugriffsprüfung weitergeleitet. Das ist ein zusätzlicher Schutz für diesen Zugangsweg. Microsoft beschreibt ihn in den Sicherheitshinweisen zum Application Proxy.

Das ist ein anderer Schutz als eine erst innerhalb der PHP-Anwendung gestartete SAML-Anmeldung. Die PHP-Sicherheitsmeldung beschreibt Angriffe, die schon vor dem Anwendungscode wirksam werden können. Entscheidend ist daher, ob eine vorgelagerte Instanz die Anfrage tatsächlich abfängt, bevor sie die PHP-Laufzeit erreicht.

Die Aussage gilt für den geschützten Proxy-Zugangsweg. Intern direkt erreichbare Backends oder zusätzliche Veröffentlichungen müssen gesondert betrachtet werden. Ebenso können berechtigte oder kompromittierte Konten durch die vorgeschaltete Anmeldung gelangen. Die Vorauthentifizierung reduziert die Angriffsfläche; das PHP-Update behebt die Schwachstellen in der Laufzeit. Deshalb haben wir trotz dieses zusätzlichen Schutzes aktualisiert.

Zuerst den laufenden Container prüfen

Wir begannen mit einer gezielten Abfrage im Webcontainer und einer begrenzten Docker-Inspektion. Dafür mussten weder Umgebungsvariablen noch Zugangsdaten ausgegeben werden.

docker exec ipam-web php -v

docker inspect ipam-web \
  --format 'image={{.Config.Image}} image_id={{.Image}}'

Unsere laufende Webinstanz meldete PHP 8.3.33. Die Imagereferenz lautete phpipam/phpipam-www:latest. Das Wort latest sagte dabei nur, welchen veränderlichen Tag wir ausgewählt hatten. Es bewies weder einen aktuellen lokalen Download noch den enthaltenen Sicherheitsstand.

Der Stack bestand aus einem Webcontainer, einem separaten Cron-Container und MariaDB. Wir bezogen deshalb beide phpIPAM-Container in die Laufzeitprüfung ein. Ein Update des Webcontainers lässt einen gesonderten Cron-Container sonst leicht auf dem vorherigen Stand zurück.

Neue Images laden und ohne Anwendung starten

Ein Pull tauscht einen bereits laufenden Container noch nicht aus. Dies nutzten wir, um die neuen Images vor der Umstellung zu untersuchen.

docker pull phpipam/phpipam-www:latest
docker pull phpipam/phpipam-cron:latest

docker run --rm --network none --entrypoint php \
  phpipam/phpipam-www:latest -v

docker run --rm --network none --entrypoint php \
  phpipam/phpipam-cron:latest -v

Durch den überschriebenen Einstiegspunkt lief jeweils nur die PHP-Versionsabfrage. Wir starteten weder den Webserver noch den Cron-Dienst und banden keine produktiven Daten ein. Für diese Prüfung war auch keine Netzwerkverbindung im Testcontainer nötig. Die verwendeten Optionen beschreibt die Docker-Befehlsreferenz.

Das Ergebnis war aufschlussreich: Beide neu geladenen Images enthielten PHP 8.3.35, gebaut im September. Der Pull hatte also einen neueren Stand geliefert, aber noch nicht das Sicherheitsupdate vom 21. Oktober. Ein anschliessender Containerneustart allein hätte unser dringendes Ziel verfehlt.

Die Image-Dokumentation von phpIPAM beschreibt latest als aktuellen stabilen Anwendungsstand mit Alpine-Sicherheitsupdates. Wie schnell ein gerade veröffentlichtes Paket in einem neu gebauten Image ankommt, mussten wir dennoch am konkreten Image prüfen.

Die korrigierten Alpine-Pakete waren schon verfügbar

Die Kandidaten basierten auf Alpine Linux. In einem weiteren temporären Container prüften wir dessen Version und aktualisierten ausschliesslich den Paketindex dieses Testcontainers. Diese Abfrage braucht Netzwerkzugriff, bleibt aber von den produktiven Daten getrennt.

docker run --rm --entrypoint sh \
  phpipam/phpipam-www:latest \
  -c 'cat /etc/alpine-release; apk update && apk policy php83 php83-apache2'

Der Container meldete Alpine 3.24.2. Installiert war php83 in Version 8.3.35-r0. Im konfigurierten Community-Repository stand bereits 8.3.36-r0 bereit. Dasselbe galt für php83-apache2, das PHP-Modul des Webservers. Damit hatten wir einen konkreten Weg: Die Paketquelle bot den Fix bereits an, obwohl er noch nicht im heruntergeladenen Anwendungsimage enthalten war.

Wir wechselten dafür weder die Alpine-Version noch die Repositories auf einen anderen Distributionszweig. Bei einer anderen Installation kann das Ergebnis anders aussehen. Wenn die passende Quelle noch keinen Fix enthält, ist ein scheinbar erfolgreicher Paketlauf kein Ersatz für den fehlenden Nachweis.

Die Korrektur in eigenen Images festhalten

Ein Paketupdate direkt im laufenden Container wäre bei dessen späterer Neuerstellung verloren gegangen. Wir bauten deshalb je ein eigenes Web- und Cron-Image. Als Ausgangspunkt verwendeten wir die zuvor geprüften Upstream-Images, jeweils über ihren vollständigen Digest festgelegt.

Ein verkürztes, übertragbares Dockerfile für den Webcontainer sieht so aus. Vor dem Build muss BASE_IMAGE auf eine geprüfte vollständige Imagereferenz gesetzt werden; ohne Wert soll der Build scheitern.

ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN apk upgrade --no-cache \
 && php -r 'exit(version_compare(PHP_VERSION, "8.3.36", ">=") ? 0 : 1);'

Für Cron verwendeten wir dasselbe Muster mit dem separaten Cron-Basisimage. Der Paketlauf aktualisierte in unserem Build PHP, das Apache-Modul und die installierten PHP-Erweiterungen auf 8.3.36-r0. Zusätzlich wurden weitere verfügbare Pakete aus demselben Alpine-Zweig aktualisiert.

Wer das Dockerfile als Dockerfile.phpfix speichert, kann den geprüften Basis-Digest beim Build übergeben. Das folgende Beispiel enthält die öffentlichen Digests aus unserem damaligen Download. Auch hier gilt: Für eine spätere Wiederholung zuerst den aktuellen Sicherheitsstand prüfen.

docker build --build-arg \
  BASE_IMAGE=phpipam/phpipam-www@sha256:96a3a6ce619169593bbd9b37887acaa425e3eb74006a5a74190808f198c85e0b \
  -t phpipam-www:phpfix-20261021 - < Dockerfile.phpfix

docker build --build-arg \
  BASE_IMAGE=phpipam/phpipam-cron@sha256:652ec7babb5211b5b93a9006ed3bb7fbb6a82973025986b6dbdadda2ce2224a9 \
  -t phpipam-cron:phpfix-20261021 - < Dockerfile.phpfix

Die Versionsbedingung am Ende liess den Build fehlschlagen, falls PHP weiterhin älter als 8.3.36 gewesen wäre. Sie prüft eine Versionsuntergrenze, keine generelle Anwendungskompatibilität. Die späteren Funktionstests blieben deshalb erforderlich.

Unsere neuen Tags nannten wir phpipam-www:phpfix-20261021 und phpipam-cron:phpfix-20261021. Beide liessen sich wieder ohne Netzwerk und ohne Datenmount mit PHP als Einstiegspunkt prüfen. Sie meldeten nun PHP 8.3.36.

Der Digest legt das Basisimage fest. Er friert aber die später aus dem Paketrepository heruntergeladenen Dateien nicht ein. Wer denselben Build Wochen später wiederholt, kann andere Paketstände erhalten. Zu unserem Betriebsnachweis gehören deshalb die Buildausgaben und die tatsächlich geprüften Images. Für einen byteidentischen Wiederaufbau wären zusätzliche Vorkehrungen nötig.

Vor der Umstellung einen Rückweg sichern

Vor dem Austausch sicherten wir den bisherigen Zustand. Unsere Sicherung prüfte zuerst den erwarteten Mountumfang und den verfügbaren Speicherplatz. Anschliessend wurden die laufenden Originalimages mit eigenen Rückfalltags versehen und archiviert. Ein neu belegter latest-Tag hätte uns sonst bei der Rücknahme wenig geholfen.

Für die konsistente Datenkopie stoppten wir Web und Cron und fuhren danach MariaDB herunter. Erst dann wurde das Datenverzeichnis archiviert. Zusätzlich sicherten wir die Containerbeschreibungen und die beschreibbaren Dateisysteme von Web und Cron. Datenbank, Anwendungsdateien, Zertifikate und SAML-Einstellungen mussten gemeinsam wieder auffindbar sein.

Die Unterscheidung zwischen Containerdateisystem und eingebundenen Daten ist dabei wichtig: Ein exportierter Container enthält die Daten seiner Mounts nicht automatisch. Die Docker-Dokumentation zu Bind-Mounts erklärt, wie Hostdateien in einen Container eingebunden werden. Wir sicherten diese Hostverzeichnisse gesondert und behielten die Zuordnung der Mounts bei.

Die Archive wurden auf Lesbarkeit geprüft und mit SHA-256-Prüfsummen kontrolliert. Anschliessend starteten wir die bisherigen Container wieder. Unser Helfer sah auch bei einem Abbruch den Neustart der Originalcontainer vor. Eine solche Kaltkopie benötigt ein Wartungsfenster; die Anwendung ist währenddessen nicht erreichbar.

Diese Prüfung bestätigte lesbare Archive und passende Prüfsummen. Ein vollständiger Restore auf einem getrennten Testsystem ist ein weiterer Nachweis. Auch Stackexport und gesondert verwaltete Variablenwerte müssen im Wiederherstellungsplan berücksichtigt werden. Solche Sicherungen können vertrauliche Daten enthalten und müssen geschützt bleiben.

Im bestehenden Stack nur die Zielimages austauschen

In Portainer änderten wir die Imagezeilen der beiden bestehenden Dienste. Die übrigen Einstellungen, insbesondere SAML-, Zertifikats- und Datenmounts, blieben erhalten. Der folgende Ausschnitt zeigt nur diese Imageänderungen und ist keine vollständige Compose-Datei.

services:
  phpipam-web:
    image: phpipam-www:phpfix-20261021
  phpipam-cron:
    image: phpipam-cron:phpfix-20261021

Für lokale Images ist entscheidend, dass sie auf dem Docker-Host vorhanden sind, auf dem Portainer den Stack startet. Unsere Vorbereitung sah deshalb ein Update ohne erneutes Nachziehen dieser Images aus einer Registry vor.

Beim späteren Stackupdate wurde dennoch die Re-pull-Option verwendet. In unserem konkreten Fall blieben die lokalen Fix-Images aktiv, wie die anschliessende Inspektion zeigte. Darauf sollte man sich nicht allgemein verlassen: Eine Registry kennt einen nur lokal gebauten Tag normalerweise nicht. Ein geplanter Registry-Pull und ein lokales Image benötigen eine bewusst passende Bereitstellung.

MariaDB als eigenen Teil des Updates behandeln

Ein gesonderter Datenbank-Imagerefresh muss unabhängig vom PHP-Fix behandelt werden. Dafür sind die tatsächlich enthaltene Serverversion, der bisherige Versionszweig und der vorgesehene Upgradepfad zu prüfen. Ein neues Image bedeutet nicht automatisch einen Hauptversionswechsel.

Eine Client-Versionsabfrage reicht dafür nicht aus. Für den Kandidaten lässt sich gezielt mariadbd mit der Versionsoption starten, ohne Netzwerk und ohne produktives Datenverzeichnis. Anschliessend kann der geprüfte Digest im Stack festgelegt werden. Kompatibilität, Support und Upgradepfad bleiben Aufgaben der jeweiligen Installation.

Die PHP-Lücken wurden durch die korrigierten PHP-Pakete behoben. Das MariaDB-Image war ein weiterer Wartungsschritt mit eigenem Rückfallbedarf. Unsere bestehenden Upgrade-Einstellungen und Datenmounts blieben erhalten.

Variablen verbessern die Ablage von Kennwörtern

Bei dieser Gelegenheit lagerten wir das Datenbank-Rootkennwort aus dem Compose-Text in eine Portainer-Variable aus. Das Muster verwendet einen verpflichtenden Wert, damit eine fehlende oder leere Variable nicht unbemerkt übernommen wird.

environment:
  - MYSQL_ROOT_PASSWORD=${IPAM_DB_ROOT_PASSWORD:?In Portainer setzen}

Die Syntax mit Doppelpunkt und Fragezeichen ist in der Compose-Dokumentation zur Variablenersetzung beschrieben. Der Wert wird gesondert in Portainer verwaltet und gehört nicht in Screenshots oder öffentliche Beispieldateien.

Eine solche Auslagerung rotiert das Kennwort einer vorhandenen Datenbank nicht. Ebenso ist eine Umgebungsvariable kein Docker Secret: Berechtigte Administratoren können sie weiterhin einsehen. Eine erforderliche Kennwortrotation muss mit einer passenden Datenbankänderung und anschliessender Funktionsprüfung separat erfolgen. Sie war kein Bestandteil unseres abgeschlossenen PHP-Updates.

Den laufenden Zustand nach dem letzten Update kontrollieren

Nach der Umstellung prüften wir erneut die tatsächlichen Imagereferenzen sowie PHP in beiden produktiven Containern. Die Abfragen mussten nach dem letzten Stackupdate stattfinden; ein früherer Test eines Kandidaten hätte den späteren Laufzeitstand nicht bewiesen.

docker inspect --format '{{.Name}} image={{.Config.Image}} status={{.State.Status}}' \
  ipam-web ipam-cron ipam-db

docker exec ipam-web php -v
docker exec ipam-cron php -v
docker exec ipam-web apk list --installed php83-apache2

Web und Cron verwendeten die vorgesehenen Fix-Images und meldeten beide PHP 8.3.36. Für den Webcontainer war ausserdem php83-apache2-8.3.36-r0 installiert. Diese zusätzliche Paketprüfung war uns wichtig: Die CLI-Ausgabe allein beschreibt zunächst den Kommandozeileninterpreter. Auch der PHP-Code ausführende Webserver musste das korrigierte Modul erhalten haben und mit den neuen Containern gestartet sein.

Danach meldeten wir uns in einem privaten Browserfenster an und öffneten die gefüllte Subnetzliste. Damit waren Anmeldung und lesender Datenzugriff nach dem Update geprüft. Gezielte Cron-Jobs, schreibende Änderungen und ein isolierter Restore sind davon getrennte Prüfungen. Für die Aussage zum PHP-Fix war der nachgewiesene Laufzeitstand massgeblich.

Lokale Fix-Images brauchen einen Wartungsplan

Bei selbst gebauten Images genügt der Verweis im Stack für eine Wiederherstellung nicht. Sie müssen gesichert, in einer geschützten Registry verfügbar oder kontrolliert neu gebaut werden können. Eine Sicherung der Originalimages deckt nicht automatisch alle später gebauten Kandidaten ab.

Die eigenen Images sind ausserdem eine übernommene Wartungsaufgabe. Künftige Upstream-Updates kommen darin nicht von selbst an. Sobald ein geprüftes reguläres phpIPAM-Image den benötigten Sicherheitsstand enthält, können wir den Rückwechsel planen. Dafür prüfen wir erneut die Laufzeit und die Anwendung, statt nur einen Tag umzubenennen.

Unser wichtigstes Ergebnis war die nachvollziehbare Kette: laufende Version feststellen, Kandidaten getrennt prüfen, tatsächlich verfügbare Pakete kontrollieren, einen Rückweg sichern und erst nach der Umstellung die produktive Laufzeit und Anmeldung abnehmen. So konnten wir den dringenden PHP-Fix einspielen, ohne eine aktuelle Imagereferenz mit einer bereits behobenen Sicherheitslücke zu verwechseln.

Erfahrungsbericht · Stand Oktober 2026 · Aus interner Dokumentation zusammengefasst und anonymisiert.