php2shell: Meine Nextcloud an einem Donnerstagmorgen
22. Okt. 2026 · @Gunnar Magholder
Donnerstag, kurz nach halb sechs. Der erste Kaffee ist noch nicht ganz durch, da laufen in den Nachrichten schon die Meldungen über eine PHP-Lücke, bei der Behörden vorsorglich ihre Webseiten abschalten. Ich schreibe eigentlich Ruby. Trotzdem steht bei mir eine private Nextcloud, und die ist nun mal PHP.
Die Kurzfassung: Zwei Fehler im PHP-Kern (CVE-2026-109618 und CVE-2026-109619, Spitzname "php2shell") erlauben es, ohne Login fremden Code auf dem Server auszuführen. Bewertet sind beide mit CVSS 9.2. Die Details sind schon am 20. Oktober durchgesickert, also bevor es den Patch gab. Am 21. kamen dann PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35.
Hier steht, wie ich meine Nextcloud im Docker-Setup durchgegangen bin. Ich hatte dabei Hilfe von Claude als Pairing-Partner, und ein paar Stolperstellen wären mir allein vermutlich erst beim zweiten Kaffee aufgefallen.
Nextcloud updaten bringt hier nichts
Mein erster Reflex wäre gewesen: Nextcloud auf die neueste Version, fertig. Das geht aber am Problem vorbei. Die Lücke steckt nicht in Nextcloud, sondern im PHP-Interpreter darunter. Und der kommt bei mir mit dem Docker-Image nextcloud:35-fpm.
Das offizielle Image baut PHP selbst aus dem Quellcode. Es hilft also nur ein neu gebautes Image, in dem das gepatchte PHP steckt. Ob das schon da ist, lässt sich prüfen, ohne den Container überhaupt zu starten:
docker pull nextcloud:35-fpm
docker run --rm --entrypoint php nextcloud:35-fpm -vDas --entrypoint php ist der Trick dabei. So läuft nicht das Nextcloud-Setup an, man fragt nur das Image ab. Steht da eine der vier gepatchten Versionen, kann es losgehen. Wenn nicht, nehme ich die Instanz vom Netz, bis das Image nachgezogen ist. Bei mir reicht dafür docker compose stop caddy, weil nur Caddy Ports nach außen hat.
Noch was, das man leicht übersieht: In meinem Setup laufen zwei Container mit diesem Image, nextcloud und cron. Beide haben PHP an Bord, beide müssen neu.
Erst nachsehen, dann patchen
Die Lücken waren gut einen Tag öffentlich, bevor es den Fix gab. Deshalb wollte ich erst wissen, ob schon jemand drin war. Die Reihenfolge ist wichtig: Beim Update kopiert das Image den Nextcloud-Code frisch ins Volume und überschreibt damit genau die Dateien, an denen man Spuren sehen würde.
Die belastbarste Prüfung ist der Integritätscheck. Nextcloud vergleicht dabei die Core-Dateien mit ihrer Signatur:
docker compose exec -u www-data nextcloud php occ integrity:check-coreDazu habe ich nach PHP-Dateien gesucht, die sich seit dem Leak geändert haben. Das Datenverzeichnis lasse ich dabei weg:
docker compose exec nextcloud find /var/www/html -name '*.php' \
-newermt '2026-10-20' -not -path '/var/www/html/data/*'Beim ersten Versuch hatte ich ein früheres Datum genommen und wurde von Treffern erschlagen. Ich hatte kurz vorher ein reguläres Update gemacht, da ist natürlich jede Datei neu. Mit dem 20. Oktober kam nichts. Das ist ein gutes Zeichen, aber kein Beweis: Wer schon auf dem Server ist, kann das Änderungsdatum mit touch fälschen. Deshalb zählt am Ende der Integritätscheck.
Bei mir war noch alles wie es soll.
Die zweite Baustelle hat mit php2shell nichts zu tun, gehört aber in denselben Morgen. Über die App "Collabora Online – Built-in CODE Server" (richdocumentscode, CVE-2026-110447) wurden schon im Oktober Webshells auf Nextcloud-Server gelegt. Betroffen ist alles vor Version 26.4.303. Nur deaktivieren reicht nicht, die App muss aktualisiert oder entfernt werden. Hier hilft der Datumsfilter nicht, weil die Angriffe früher liefen. Also:
docker compose exec -u www-data nextcloud php occ app:list | grep -i richdocuments
docker compose exec nextcloud find /var/www/html/apps /var/www/html/custom_apps \
-path '*richdocumentscode*' -name 'sh[xr]*.php'Da bin ich nur drüber gefallen weil ich eh nach Sicherheitsdingen suchte - im Einsatz hab ich das bei meiner Nextcloud nicht, aber vielleicht hilft es ja jemandem.
Das eigentliche Patchen
Bevor irgendwas neu startet, kommt ein Backup. Das Nextcloud-Image macht beim Start selbst ein Upgrade, wenn im neuen Image eine neuere 35.0.x steckt. Dann wird auch die Datenbank migriert, und ein Zurück gibt es nur mit Dump.
BK=backup/$(date +%F-%H%M); mkdir -p "$BK"; chmod 700 backup "$BK"
docker compose exec -u www-data nextcloud php occ maintenance:mode --on
docker compose exec -T db sh -c \
'mariadb-dump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction nextcloud' \
> "$BK/nextcloud-db.sql"
docker compose exec -T nextcloud tar czf - -C /var/www/html config custom_apps \
> "$BK/nc-config-apps.tgz"
tail -n1 "$BK/nextcloud-db.sql" # sollte "-- Dump completed" zeigenDas -T bei exec habe ich früher gern vergessen. Ohne das hängt Docker ein TTY dazwischen, und der Dump ist im schlimmsten Fall kaputt, ohne dass man es merkt. Die Nutzerdaten unter data/ sichere ich hier nicht mit. Die fasst das Update nicht an, und dafür gibt es ein eigenes Backup. (Gibt es doch, oder?)
Dann das Update selbst:
docker compose up -d nextcloud cron
docker compose logs -f nextcloud
docker compose exec -u www-data nextcloud php occ status
docker compose exec -u www-data nextcloud php occ app:update --all
docker compose exec -u www-data nextcloud php occ maintenance:mode --offUnd zum Schluss nachsehen, ob wirklich das neue PHP läuft, in beiden Containern:
docker compose exec nextcloud php -v
docker compose exec cron php -v
docker compose exec -u www-data nextcloud php occ integrity:check-coreWas ich mitnehme
Die wichtigste Erkenntnis ist banal und trotzdem leicht zu übersehen: Bei Docker-Setups ist die Laufzeitumgebung Teil des Images. Ein App-Update hilft nur, wenn auch das Image darunter frisch ist. Und ob es das ist, sagt mir php -v im Container, nicht das Tag.
Beim Thema Tags bin ich zwiegespalten. Meine Nextcloud läuft auf nextcloud:35-fpm, einem Tag, das mitwandert. Heute war das praktisch: Pull, neu starten, fertig. Sonst sorgt genau das dafür, dass ein Neustart plötzlich ein Upgrade macht, um das ich nicht gebeten habe. Bei anderen Diensten pinne ich deshalb per Digest. Dann muss ich in so einem Fall aber dran denken, den Digest selbst zu tauschen, sonst zieht docker compose pull brav das alte, verwundbare Image. Eine perfekte Lösung habe ich nicht. Ich werde die Nextcloud wohl auch pinnen und mir dafür einen Hinweis in die Compose-Datei schreiben.
Und: erst nachsehen, dann patchen. Das Update verwischt genau die Spuren, die man vorher noch hätte finden können.