PHP2Shell auf ARM64: PHP-Sicherheitsupdate für Debian selbst bauen und ausrollen

Illustration: ARM64-Server mit Prozessor, Softwarepaket mit Zahnrad und rotem Warnsymbol zum PHP-SicherheitsupdateKI-generiert · Google C2PA Core Generator Library

TL;DR. Wenn das PHP-Repository für ARM64 hinterherhinkt: PHP2Shell-Fix sauber selbst bauen und kontrolliert ausrollen.

Problem Für PHP2Shell (CVE-2026-109618/109619) gibt es gepatchte Pakete 8.4.27, 8.3.36 und 8.2.35. Auf packages.sury.org fehlen für Debian die arm64-Binärpakete noch (Stand 22.10.2026, 13:01 Uhr).
Wer betroffen ist Debian-Server (11, 12, 13) auf ARM64, etwa Ampere- oder Graviton-Instanzen, die PHP aus packages.sury.org beziehen. Ubuntu-Server mit dem PPA ondrej/php sind nicht betroffen, dort sind die arm64-Pakete bereits da.
Lösung Das bereits veröffentlichte Quellpaket in einer Chroot für arm64 nachbauen, mit Version -0+local1 so einordnen, dass das offizielle Paket den Eigenbau später automatisch ersetzt, und Server für Server ausrollen.
Aufwand Etwa 1,5 bis 2 Stunden Bauzeit je PHP-Version, dazu Pilot-Test, Rollout und Rückbau.
Unterstützung Sie möchten das nicht selbst übernehmen? Wir unterstützen Sie bei Update und Absicherung, etwa im Rahmen unserer WordPress-Wartung.

Veröffentlicht: 22.10.2026 · Zuletzt aktualisiert: 22.10.2026, 14:00 Uhr

Für die kritischen PHP-Lücken PHP2Shell (CVE-2026-109618 und CVE-2026-109619) hat das PHP-Team am 21.10.2026 die Versionen 8.5.12, 8.4.27, 8.3.36 und 8.2.35 veröffentlicht (php.net ChangeLog). Viele Debian-Server beziehen PHP nicht aus der Distribution, sondern aus dem Repository packages.sury.org. Dort sind die gepatchten Pakete für amd64 seit dem 21.10.2026 verfügbar, für arm64 noch nicht. Auf ARM64-Servern meldet apt deshalb weiterhin nur die alte, verwundbare Version.

Diese Anleitung richtet sich an Server-Administratoren und zeigt, wie Sie die Lücke überbrücken: Sie bauen das bereits veröffentlichte Quellpaket selbst für arm64, versionieren es so, dass das spätere offizielle Paket den Eigenbau automatisch ersetzt, rollen es kontrolliert aus und bauen es nach dem offiziellen Release rückstandsfrei wieder ab.

Aktueller Repository-Status (geprüft am 22.10.2026, 14:49 Uhr)

Wir haben die Paket-Indizes direkt geprüft (Datei Packages je Architektur gegen die Datei Sources):

PHP-Version Quellpaket amd64 arm64
8.4 (php8.4-fpm) 8.4.27 8.4.27 (gepatcht) 8.4.26 (verwundbar)
8.3 (php8.3-fpm) 8.3.36 8.3.36 (gepatcht) 8.3.35 (verwundbar)
8.2 (php8.2-fpm) 8.2.35 8.2.35 (gepatcht) 8.2.34 (verwundbar)

Das Bild ist für Debian 11 (bullseye), 12 (bookworm) und 13 (trixie) gleich. Im Ubuntu-PPA ondrej/php sind 8.4.27, 8.3.36 und 8.2.35 für Ubuntu 22.04 und 24.04 dagegen bereits auch für arm64 veröffentlicht. Dort genügt ein normales apt upgrade.

Prüfen Sie vor dem Start selbst, ob die offiziellen Pakete inzwischen erschienen sind (Abschnitt 1). Ist das der Fall, brauchen Sie diese Anleitung nicht, ein reguläres Update ist immer vorzuziehen.

Alternative über Debian-Security: Debian selbst liefert gepatchte Pakete, auch für arm64: php8.2 8.2.35-1~deb12u1 für Debian 12 (DLA-4848-1) und php8.4 8.4.27-1~deb13u1 für Debian 13 (DSA-6574-1). Wenn Ihre Anwendungen mit genau dieser PHP-Version laufen, kann ein Wechsel auf die Distributionspakete der schnellere Weg sein. Für PHP 8.3 oder 8.4 auf Debian 12 gibt es diese Abkürzung nicht.

Voraussetzung und Überbrückung

Das Vorgehen setzt voraus, dass das Repository das Quellpaket der gepatchten Version bereits veröffentlicht hat (deb-src). Das ist bei packages.sury.org derzeit der Fall. Der Nachbau ist dann inhaltlich identisch mit dem, was der Repository-Betreiber später selbst für arm64 ausliefert: Die Debian-Patches sind bereits an die neue Upstream-Version angepasst.

Ein Eigenbau dauert mehrere Stunden. Setzen Sie bis dahin die Sofortmaßnahmen aus dem Advisory um, vor allem session.upload_progress.enabled = Off mit Neustart der PHP-Worker. Wie das je Betriebsart funktioniert, steht in unserem Beitrag zu PHP2Shell. Diese Maßnahmen ersetzen den Patch nicht, verkleinern aber das Zeitfenster.

1. Prüfen: Was fehlt eigentlich?

Auf einem arm64-Server:

apt-get update
apt-cache policy php8.4-fpm                                # Binärpaket: welche Version gibt es für arm64?
apt-cache showsrc php8.4 | grep -E '^(Version|Binary):'    # Quellpaket: welche Version gibt es?

Typisches Bild in dieser Situation: apt-cache policy zeigt als Kandidaten noch die alte Version, apt-cache showsrc bereits die neue (dafür ist eine deb-src-Zeile für das Repository nötig). Architekturunabhängige Pakete (Architecture: all) sind oft schon in der neuen Version da, es fehlen nur die arm64-Binärpakete.

2. Den Build-Host wählen

PHP ist ein großes Paket: debian/rules kompiliert den Quellbaum für jede SAPI separat (apache2, cgi, cli, embed, fpm, litespeed, phpdbg) samt Erweiterungen. Erfahrungswerte aus einem Praxislauf auf einer arm64-Cloud-Instanz (Neoverse N1, 8 Kerne):

Ressourcen Bauzeit je PHP-Version
3 parallele Jobs (zwei Versionen gleichzeitig) ca. 2 Stunden
5 parallele Jobs (eine Version allein) ca. 1 Stunde 20 Minuten

Für drei PHP-Versionen sollten Sie also etwa 3,5 Stunden einplanen. Die Chroot belegte am Ende rund 7 bis 8 GB, der Arbeitsspeicherbedarf lag bei etwa 2,5 GB. Ihre Werte können je nach Hardware abweichen.

  • Gleiche Architektur und gleiches Release wie die Zielsysteme (hier: arm64, Debian 12). Cross-Compiling ist möglich, aber unnötig kompliziert.
  • Möglichst viele Kerne, ausreichend RAM und mindestens 15 GB freier Platz.
  • Steht kein dedizierter Build-Server zur Verfügung, ist ein Produktivserver mit geringer Last vertretbar, aber nur mit Ressourcengrenzen (Abschnitt 6).

3. Die Build-Chroot anlegen

Build-Abhängigkeiten gehören nicht auf ein Produktivsystem. Eine Chroot per debootstrap hält alles getrennt; auf dem Host landet nur das Paket debootstrap.

#!/bin/bash
# setup-chroot.sh: legt eine bookworm-arm64-Build-Chroot an
set -euo pipefail
B=/srv/php-build          # Arbeitsverzeichnis
C=$B/chroot
MIRROR=http://deb.debian.org/debian

mkdir -p $B
command -v debootstrap >/dev/null || apt-get install -y --no-install-recommends debootstrap

[ -x $C/usr/bin/apt-get ] || \
  nice -n 19 ionice -c3 debootstrap --variant=buildd --arch=arm64 bookworm $C $MIRROR

cp /etc/resolv.conf $C/etc/resolv.conf
cat > $C/etc/apt/sources.list <<EOF
deb $MIRROR bookworm main
deb $MIRROR bookworm-updates main
deb http://security.debian.org/debian-security bookworm-security main
EOF

mountpoint -q $C/proc    || mount -t proc proc $C/proc
mountpoint -q $C/dev/pts || mount -t devpts devpts $C/dev/pts -o newinstance,ptmxmode=0666

chroot $C /bin/bash -c 'export DEBIAN_FRONTEND=noninteractive
  apt-get update
  apt-get install -y --no-install-recommends ca-certificates devscripts quilt equivs dpkg-dev fakeroot xz-utils curl'

# PHP-Repository mit Quell-Indizes eintragen (Keyring vom Host übernehmen)
mkdir -p $C/usr/share/keyrings
cp /usr/share/keyrings/deb.sury.org-php.gpg $C/usr/share/keyrings/
cat > $C/etc/apt/sources.list.d/php.list <<EOF
deb     [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ bookworm main
deb-src [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ bookworm main
EOF
chroot $C apt-get update
  • --variant=buildd installiert build-essential gleich mit.
  • Falle: Manche Hoster-Mirrors führen keine deb-src-Indizes. Ein deb-src-Eintrag darauf bricht apt-get update mit 404 ab. Debian-Quellen brauchen Sie hier nicht, nur die des PHP-Repositorys.
  • Für Debian 13 ersetzen Sie bookworm durch trixie (und im Folgenden debian12 durch debian13), für Debian 11 entsprechend durch bullseye und debian11.
  • Prüfen Sie den Pfad des Keyrings auf Ihrem Host, er kann je nach Einrichtung abweichen.

4. Quellpakete holen und verifizieren

C=/srv/php-build/chroot
for p in php8.4 php8.3 php8.2; do
  mkdir -p $C/build/$p
  chroot $C bash -c "cd /build/$p && apt-get source --download-only $p && sha256sum *.orig*.tar.*"
done

apt-get source prüft die Signatur der .dsc-Datei gegen den Repository-Schlüssel. Gleichen Sie zusätzlich den Upstream-Tarball gegen php.net ab; die Prüfsummen stehen in der Release-Übersicht als JSON:

curl -s 'https://www.php.net/releases/index.php?json&version=8.4' | python3 -m json.tool | grep -A3 'tar.xz' 

Die sha256-Summe von php8.4_<version>.orig.tar.xz muss mit der von php-<version>.tar.xz auf php.net übereinstimmen. Nach dem Entpacken im Bau-Schritt (Abschnitt 6) steht der Fix außerdem in der Datei NEWS des Quellbaums:

head -20 /srv/php-build/chroot/build/php8.4/src/NEWS

Wer es genau wissen will, vergleicht den Quellbaum mit den Commits aus den Advisories GHSA-6wmf-4rv5-p3rp und GHSA-w8hw-w9fx-j79w.

5. Versionierung: der wichtigste Teil

Die Versionsnummer des Eigenbaus muss zwei Bedingungen erfüllen: Sie muss höher sein als die installierte, verwundbare Version (sonst installiert apt den Eigenbau nicht) und niedriger als die künftige offizielle arm64-Version (sonst ersetzt apt den Eigenbau später nicht automatisch). Das gelingt mit der Debian-Revision 0 und einem eigenen Suffix:

Stand Version
installiert (verwundbar) 8.4.26-1+0~20260925.56+debian12~1.gbp80d95d
Eigenbau 8.4.27-0+local1~debian12
künftig offiziell (Quellpaket bereits sichtbar) 8.4.27-1+0~20261021.57+debian12~1.gbp7636b9

Gegenüber dem installierten Paket entscheidet die Upstream-Version: 8.4.27 ist größer als 8.4.26. Gegenüber dem offiziellen Paket ist die Upstream-Version gleich, dann entscheidet die Debian-Revision: 0+local1… ist kleiner als 1+0~…. Prüfen Sie das vorher immer gegen beide Seiten, mit den Versionen von Ihrem Server:

dpkg --compare-versions 8.4.27-0+local1~debian12 gt 8.4.26-1+0~20260925.56+debian12~1.gbp80d95d && echo "OK: neuer als installiert"
dpkg --compare-versions 8.4.27-0+local1~debian12 lt 8.4.27-1+0~20261021.57+debian12~1.gbp7636b9 && echo "OK: älter als offiziell"

Achtung: Debian-Security als dritter Lieferant

Debian 12 liefert selbst php8.2 aus, und genau hier gibt es mit 8.2.35-1~deb12u1 bereits ein Sicherheitsupdate. Dessen Revision 1 ist höher als die Revision 0 des Eigenbaus, und die Version ist höher als die installierte 8.2.34 aus packages.sury.org. Ist Debian-Security in Ihren Quellen eingetragen, kann unattended-upgrades deshalb nachts ungetestet den Paketlieferanten wechseln und die Dienste neu starten. Für Debian 13 gilt das Gleiche mit php8.4. Prüfen Sie daher vor jedem Rollout alle Quellen:

apt-cache policy php8.2-fpm
apt-get -s upgrade | grep php8.2

Abhilfe ist ein apt-Pin, der Debian-Security für diese Pakete unter die Priorität der installierten Version (100) drückt:

# /etc/apt/preferences.d/php82-keep-thirdparty.pref
# PHP 8.2 aus dem PHP-Repository beziehen, nicht aus Debian-Security
Package: php8.2*
Pin: release l=Debian-Security
Pin-Priority: 50

l=Debian-Security ist das Release-Label der Security-Quelle; Sie sehen es in der Ausgabe von apt-cache policy. Explizite Installationen, etwa ein Rollback auf eine bestimmte Version, funktionieren mit dem Pin weiterhin. Ob der Pin nach dem offiziellen Release bleibt, ist eine bewusste Entscheidung (Abschnitt 11).

6. Bauen

Build-Skript pro Quellpaket (läuft in der Chroot)

#!/bin/bash
# /usr/local/bin/build-one.sh <quellpaket> <upstream-version> <jobs>
set -euo pipefail
P=$1; UP=$2; J=$3
NEW="${UP}-0+local1~debian12"

cd /build/$P
rm -rf src
dpkg-source -x ${P}_*.dsc src
cd src

# Sicherheitsnetz: Eigenbau muss UNTER der offiziellen Version liegen
OLD=$(dpkg-parsechangelog -S Version)
if ! dpkg --compare-versions "$NEW" lt "$OLD"; then
  echo "ABBRUCH: $NEW ist nicht kleiner als $OLD"; exit 3
fi

export DEBEMAIL="build@example.org" DEBFULLNAME="Local arm64 rebuild"   # Platzhalter: eigene Adresse
dch -v "$NEW" -D bookworm --force-distribution --force-bad-version \
  "Local arm64 rebuild of repository source $OLD (no arm64 binaries published yet)."
dch -a "Security release PHP $UP. Upstream tarball sha256 verified against php.net."
dch -a "Version deliberately lower than the official build so apt replaces it automatically."

export DEB_BUILD_OPTIONS="nocheck parallel=$J"
dpkg-buildpackage -B -uc -us
  • -B baut nur architekturabhängige Pakete; die Architecture: all-Pakete kommen bereits aus dem Repository.
  • -uc -us: unsigniert, denn die Pakete werden direkt als Datei installiert (Abschnitt 7).
  • nocheck überspringt die PHP-Testsuite. Das spart viel Zeit, verlangt aber einen gründlichen Funktionstest auf einem Pilot-Server (Abschnitt 8).

Orchestrierung auf dem Host

#!/bin/bash
# run-all.sh: Build-Abhängigkeiten installieren, dann bauen
set -uo pipefail
B=/srv/php-build; C=$B/chroot
exec >>$B/run.log 2>&1

mountpoint -q $C/proc    || mount -t proc proc $C/proc
mountpoint -q $C/dev/pts || mount -t devpts devpts $C/dev/pts -o newinstance,ptmxmode=0666
install -m 755 $B/build-one.sh $C/usr/local/bin/build-one.sh

for p in php8.4 php8.3 php8.2; do
  chroot $C bash -c "cd /build/$p && rm -rf bd && dpkg-source -x ${p}_*.dsc bd \
    && cd bd && DEBIAN_FRONTEND=noninteractive apt-get build-dep -y --arch-only ./" \
    && echo "build-dep $p ok" || { echo "build-dep $p FEHLER"; exit 1; }
  rm -rf $C/build/$p/bd
done

# Wichtigste Versionen zuerst und parallel, danach der Rest mit mehr Jobs
chroot $C build-one.sh php8.4 8.4.27 3 > $B/build-php8.4.log 2>&1 & P1=$!
chroot $C build-one.sh php8.3 8.3.36 3 > $B/build-php8.3.log 2>&1 & P2=$!
wait $P1; echo "== php8.4 rc=$?"; wait $P2; echo "== php8.3 rc=$?"
chroot $C build-one.sh php8.2 8.2.35 5 > $B/build-php8.2.log 2>&1; echo "== php8.2 rc=$?"

Mit Ressourcengrenzen starten

Als transiente systemd-Unit läuft der Build unabhängig von der SSH-Sitzung und lässt sich begrenzen:

systemd-run --unit=php-build \
  --property=Nice=19 --property=IOSchedulingClass=idle \
  --property=CPUWeight=10 --property=MemoryMax=7G \
  bash /srv/php-build/run-all.sh

# Fortschritt prüfen
systemctl is-active php-build; grep '^==' /srv/php-build/run.log

Harmlos im Log sind make[1]: [debian/rules:…: unprepared] Error 1 (ignored), perl-Warnungen Setting locale failed in der Chroot und viele -Wpedantic-Warnungen aus zend_vm_execute.h.

Pro PHP-Version entstehen rund 32 bis 34 arm64-Binärpakete (cli, fpm, cgi, common, curl, gd, intl, mbstring, mysql, opcache, xml, zip und weitere) plus -dbgsym-Pakete, die Sie nicht verteilen müssen. Erweiterungen aus eigenen Quellpaketen (apcu, igbinary, redis, imagick, memcache) müssen bei einem Patch-Release nicht neu gebaut werden: Die Zend-API-Version bleibt innerhalb eines Minor-Release gleich. Prüfen Sie das trotzdem auf dem Pilot-Server über den Modulvergleich (Abschnitt 8).

7. Paketverzeichnis erzeugen und Rollback-Reserve sichern

#!/bin/bash
# mkrepo.sh php8.4 php8.3 [php8.2]: sammelt fertige .debs ohne dbgsym
set -euo pipefail
B=/srv/php-build; C=$B/chroot
mkdir -p $B/repo
for p in "$@"; do
  ls $C/build/$p/*local1*_arm64.changes >/dev/null      # nur vollständig gebaute Versionen
  for f in $C/build/$p/*local1*.deb; do
    case $f in *-dbgsym_*) ;; *) cp -u "$f" $B/repo/ ;; esac
  done
done
cd $B/repo && sha256sum *.deb > SHA256SUMS

Bewusst entsteht daraus keine apt-Quelle. Die .deb-Dateien werden direkt installiert: So bleibt keine zusätzliche Paketquelle zurück, und apt kennt den Eigenbau nur als installierte Version (Priorität 100). Erscheint das offizielle Paket mit höherer Version (Priorität 500), ersetzt apt den Eigenbau beim nächsten Upgrade von selbst.

Sichern Sie vor jeder Installation die aktuell installierten Pakete. Ein Rollback per apt funktioniert nur, solange das Repository die alten Versionen noch im Index führt:

mkdir -p /srv/php-build/old && cd /srv/php-build/old
apt-get download $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}=${Version}\n' 'php8.[234]*' | awk '/^ii/{print $2}')
sha256sum *.deb > SHA256SUMS

8. Installation auf einem Server

Das Skript installiert nur Pakete, die auf dem Server schon vorhanden sind und aus dem PHP-Quellpaket stammen. So kommt kein neues Paket hinzu, und Fremd-Erweiterungen bleiben unberührt.

#!/bin/bash
# php-install-local.sh <paketverzeichnis> <8.4|8.3|8.2>...
set -euo pipefail
R=$1; shift
cd "$R"; sha256sum -c SHA256SUMS --quiet || { echo "SHA256 FEHLER"; exit 2; }

LIST=()
for v in "$@"; do
  for p in $(dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${source:Package}\n' \
               "php$v*" "libapache2-mod-php$v" "libphp$v-embed" 2>/dev/null \
             | awk -v s="php$v" '/^ii/ && $3==s {print $2}'); do
    f=$(ls ${p}_*local1*_arm64.deb 2>/dev/null || true)
    [ -n "$f" ] && LIST+=("$R/$f")
  done
done

echo "Installiere ${#LIST[@]} Pakete:"; printf '  %s\n' "${LIST[@]##*/}"
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
  -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef "${LIST[@]}"

--force-confold und --force-confdef lassen die Konfiguration unter /etc/php unverändert, Ihre PHP2Shell-Härtung bleibt also aktiv. Die Paketskripte starten phpX.Y-fpm automatisch neu. Laufende Requests, etwa ein gerade laufendes WordPress-Update, können dabei abbrechen. Prüfen Sie danach auf hängende .maintenance-Dateien.

Vorher-Stand sichern und danach prüfen

mkdir -p /root/php-before
dpkg-query -W -f='${db:Status-Abbrev} ${Package}=${Version}\n' 'php8.[234]*' | awk '/^ii/{print $2}' > /root/php-before/pkgs.txt
for v in 8.2 8.3 8.4; do php-fpm$v -m > /root/php-before/fpm$v-m.txt; done
T0=$(date +%s)   # mit der Uhr des Zielservers

# ... Installation ...

for v in 8.2 8.3 8.4; do
  php-fpm$v -v | head -1                                         # neue Version?
  php-fpm$v -t 2>&1 | tail -1                                    # Konfiguration gültig?
  systemctl is-active php$v-fpm                                  # Dienst läuft?
  php-fpm$v -m | diff -q /root/php-before/fpm$v-m.txt - && echo "$v: Module unverändert"
  php-fpm$v -i | grep -E '^session.upload_progress.enabled'      # Härtung noch aktiv?
done
dpkg --audit && echo "dpkg sauber"
systemctl list-units 'php*' --state=failed --no-legend | wc -l    # muss 0 sein
find /var/www -maxdepth 6 -name .maintenance -newermt @$T0         # muss leer sein

Testen Sie jede Website direkt gegen den lokalen Webserver und vergleichen Sie Statuscode und Antwortzeit mit dem Stand vorher. Ersetzen Sie dabei die Platzhalter <listen-ip> und <domain>:

curl -sk -o /dev/null -w '%{http_code} %{time_total}s\n' --connect-to ::<listen-ip>: https://<domain>/

Falle: Lauscht nginx nur auf einer bestimmten IP, landet ein Test gegen 127.0.0.1 beim Default-vhost. Verwenden Sie die Listen-IP aus der vhost-Konfiguration.

Pilot-Server: Weil die Testsuite übersprungen wurde, gehört auf den ersten Server ein Funktionstest aller FPM-Pools, am einfachsten direkt über den Socket mit einem FastCGI-Client wie cgi-fcgi aus libfcgi-bin. Ein Testskript sollte die wichtigsten Erweiterungen benutzen: session, gd, opcache, mbstring, intl, json, xml, zip, sqlite3, mysqli und curl. Das Skript muss für den Pool-User lesbar sein; unter /root antwortet FPM mit Primary script unknown. Nutzen Sie etwa /srv/phptest/ mit 755/644 und löschen Sie es danach.

9. Im Netzwerk ausrollen

  1. Pilot: ein Server ohne produktive Last, mit dem ausführlichen Test aus Abschnitt 8.
  2. Server mit wenig Last, danach Server für Server nach steigender Bedeutung.
  3. Der Server mit dem meisten Traffic zuletzt.

Werten Sie zwischen den Servern die Prüfungen aus. Schlägt etwas fehl: anhalten, nicht weiterrollen. Ein Rollout-Skript vom Management-Server aus:

#!/bin/bash
# rollout.sh <host> "<domain1 domain2 ...>"
set -uo pipefail
H=$1; DOMAINS=$2
A=/srv/php-build-dist            # lokale Kopie: .debs, SHA256SUMS, php-install-local.sh
IP=$(getent ahostsv4 "$H" | awk 'NR==1{print $1}')
SSH="ssh -4 root@$IP"
LISTEN_IP=${LISTEN_IP:-$IP}      # IP, auf der der Webserver lauscht

echo "== $H ($IP)"
# 1) Gibt es inzwischen das offizielle Paket? Dann normales Upgrade statt Eigenbau.
$SSH 'apt-get update -qq; apt-cache policy php8.4-fpm | grep Candidate' </dev/null

# 2) Pin gegen den Lieferantenwechsel setzen
$SSH 'cat > /etc/apt/preferences.d/php82-keep-thirdparty.pref <<EOF
Package: php8.2*
Pin: release l=Debian-Security
Pin-Priority: 50
EOF
apt-cache policy php8.2-fpm | grep Candidate' </dev/null

# 3) Pakete übertragen und auf dem Ziel verifizieren
scp -4 -q -r "$A" root@$IP:/root/php-local
$SSH 'cd /root/php-local && sha256sum -c SHA256SUMS --quiet && echo SHA-OK' </dev/null || exit 1

# 4) Vorher-Stand, Installation, Prüfungen (Abschnitt 8)
$SSH "bash -s" < ./host-steps.sh   # Platzhalter: Ihr Skript mit den Schritten aus Abschnitt 8
for d in $DOMAINS; do
  $SSH "curl -sk -o /dev/null -w '$d %{http_code} %{time_total}s\n' --connect-to ::$LISTEN_IP: https://$d/" </dev/null
done
  • In Schleifen per getent ahostsv4 auflösen und mit -4 verbinden, sonst scheitert scp sporadisch an IPv6 oder einem langsamen Resolver.
  • </dev/null bei ssh in Schleifen, sonst verschluckt ssh den stdin der Schleife.
  • Zeitstempel vom Zielserver nehmen, nicht vom Management-Server.
  • Mit Ansible oder Salt bilden Sie dieselben Schritte als Playbook mit serial: 1 und max_fail_percentage: 0 ab.

Nehmen Sie nach dem Rollout Interim-Maßnahmen zurück, die nur für das Patch-Zeitfenster gedacht waren (etwa pm.max_requests = 1 oder abgeschaltete Sites). Dauerhaft sinnvolle Härtungen aus dem Advisory bleiben aktiv.

10. Rollback

#!/bin/bash
# php-rollback-local.sh <pkgs-vorher.txt> <8.4|8.3|8.2>... [--simulate]
set -euo pipefail
F=$1; shift; SIM=""; VS=()
for a in "$@"; do [ "$a" = "--simulate" ] && SIM="-s" || VS+=("$a"); done

LIST=()
for v in "${VS[@]}"; do
  for p in $(dpkg-query -W -f='${db:Status-Abbrev} ${Package} ${Version}\n' "php$v*" \
             | awk '/^ii/ && $3 ~ /local1/ {print $2}'); do
    old=$(grep "^$p=" "$F" | cut -d= -f2-)
    [ -n "$old" ] && LIST+=("$p=$old") || echo "WARN: keine Vorher-Version für $p"
  done
done
printf '  %s\n' "${LIST[@]}"
DEBIAN_FRONTEND=noninteractive apt-get install $SIM -y --allow-downgrades \
  -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef "${LIST[@]}"

Lassen Sie das Skript immer zuerst mit --simulate laufen, dann ausführen, danach dieselben Prüfungen wie in Abschnitt 8. Führt das Repository die alten Versionen nicht mehr im Index, installieren Sie die in Abschnitt 7 gesicherten .deb-Dateien mit apt-get install --allow-downgrades ./<paket>.deb, nach erfolgreichem sha256sum -c. Bitte rollen Sie nur zurück, wenn es zwingend nötig ist: Die alte Version ist verwundbar.

11. Rückbau nach dem offiziellen Release

Falle: Ein Wächter, der nur den Candidate aus apt-cache policy auswertet, meldet nach der Installation des Eigenbaus sofort „neue Version verfügbar“, denn der Kandidat ist jetzt die installierte Eigenbau-Version. Suchen Sie stattdessen gezielt nach der offiziellen Revision:

apt-get update -qq
apt-cache madison php8.4-fpm | grep -E '8\.4\.27-1\+0~' 

Weil die offizielle Revision höher ist, genügt ein Upgrade gezielt nur der versionierten Pakete, damit Metapakete nicht ungewollt eine neue PHP-Version nachziehen:

apt-get install --only-upgrade $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.[234]-*' 'php8.[234]' | awk '/^ii/{print $2}')
dpkg -l 'php8.[234]*' | awk '/^ii/ && $3 ~ /local1/' | wc -l    # muss 0 sein

Pin entfernen oder bewusst behalten: Stehen alle php8.2-Pakete auf der offiziellen Version des PHP-Repositorys, kann der Pin weg. Debian-Security kann aber auch künftig eine Version vor dem PHP-Repository veröffentlichen. Wer PHP konsequent aus einer Quelle beziehen will, lässt den Pin dauerhaft stehen und dokumentiert ihn.

Aufräumen: Die Build-Chroot (7 bis 8 GB) und das Paket debootstrap entfernen Sie, sobald das offizielle Paket überall installiert ist. Prüfen Sie vorher, dass nichts mehr eingehängt ist (mount | grep php-build muss leer sein), und führen Sie nie rm -rf auf eine Chroot mit eingehängtem proc oder dev/pts aus. Paketverzeichnisse auf den Zielservern löschen Sie nach dem Upgrade, Vorher-Stände und gesicherte Altpakete einige Tage später. Kontrollieren Sie am Morgen nach jedem Schritt, was unattended-upgrades mit PHP gemacht hat:

grep -h php8 /var/log/unattended-upgrades/unattended-upgrades.log | tail -5
apt-cache policy php8.2-fpm | grep -E 'Installed|Candidate' 

Checkliste

  • ☐ Offizielle arm64-Pakete wirklich noch nicht da? (apt-cache policy gegen apt-cache showsrc)
  • ☐ Alternative Debian-Security-Paket geprüft
  • ☐ Sofortmaßnahmen aus dem Advisory aktiv, Überwachung läuft
  • ☐ Build-Host mit gleicher Architektur, mindestens 15 GB frei, Ressourcengrenzen gesetzt
  • ☐ Upstream-Tarball per sha256 gegen php.net geprüft, Fix in NEWS nachgewiesen
  • ☐ Version -0+local1 mit dpkg --compare-versions gegen beide Seiten geprüft
  • ☐ Debian-Security als weitere Quelle geprüft, ggf. Pin gesetzt
  • ☐ Alte Pakete als .deb und Vorher-Stand je Server gesichert
  • ☐ Pilot-Server mit FastCGI-Funktionstest aller Pools
  • ☐ Rollout Server für Server, Traffic-stärkster zuletzt
  • ☐ Wächter prüft auf die offizielle Revision, nicht auf den Candidate
  • ☐ Rückbau terminiert: Upgrade, Pin-Entscheidung, Chroot löschen

Haftungsausschluss

Diese Anleitung dient ausschließlich der Information und erfolgt ohne Gewähr für Vollständigkeit, Richtigkeit und Eignung für Ihre konkrete Umgebung. Die Anwendung erfolgt auf eigene Verantwortung und auf eigenes Risiko. Legen Sie vor jeder Änderung vollständige Backups an und testen Sie das Vorgehen zuerst auf einem Testsystem. Ein selbst gebautes Paket ist kein offiziell unterstütztes Paket des Distributors oder des Repository-Betreibers. CMS ADMINS übernimmt im Rahmen des gesetzlich Zulässigen keine Haftung für Schäden, Datenverlust oder Ausfälle, die aus der Anwendung dieser Anleitung entstehen. Die Haftung für Vorsatz und grobe Fahrlässigkeit bleibt unberührt.

Fazit

Ein Eigenbau von PHP-Sicherheitsupdates ist überschaubar, wenn das Repository das Quellpaket bereits bereitstellt. Die eigentliche Arbeit steckt in der richtigen Versionsnummer, damit sich der Eigenbau später selbst abräumt, im Blick auf alle Paketquellen, damit kein automatisches Update den Lieferanten wechselt, und in einem disziplinierten Rollout mit Pilot, Prüfungen und Rückbauplan. So bleibt das Zeitfenster der Lücke kurz, auch wenn die offiziellen Pakete für Ihre Architektur noch fehlen.

Sie sind unsicher, ob Ihre Website betroffen ist? In unserem Beitrag PHP2Shell: So testen Sie, ob Ihr Hoster PHP gepatcht hat finden Sie einen einfachen Selbsttest. Wenn Sie Unterstützung bei Update oder Absicherung brauchen, sprechen Sie uns an.

Vorheriger Beitrag
PHP2Shell: So testen Sie selbst, ob Ihr Hoster PHP gepatcht hat
Nächster Beitrag
WordPress, Drupal und Co.: Das ist neu im Oktober 2026