PHP-Sicherheitslücke prüfen Kärnten: Audit-Skript für CVE-2026-109618 und -109619
Zwei kritische PHP-Lücken (CVSS 9,2) erlauben Remote Code Execution. Prüfen Sie Ihren Server mit unserem Read-only-Skript – Hilfe in Kärnten anfragen.
Wissen Sie, welche PHP-Versionen auf Ihrem Server gerade laufen – nicht installiert, sondern tatsächlich im Speicher? Bei den meisten Kärntner Betrieben lautet die ehrliche Antwort: ungefähr. Am 21. Oktober 2026 hat das PHP-Team ein dringendes Sicherheitsupdate für zwei Schwachstellen im PHP-Interpreter veröffentlicht, und „ungefähr” reicht jetzt nicht mehr.
Beide Lücken, oft als „PHP2Shell” bezeichnet, haben einen CVSS-Score von 9,2. Im schlimmsten Fall ermöglichen sie ungeauthentifizierte Remote Code Execution (RCE, das Ausführen fremden Codes aus der Ferne) direkt auf dem Server. Die Frage ist selten, ob ein Update existiert, sondern ob es auch dort angekommen ist, wo es zählt. Genau hier setzt unser Read-only-Audit-Skript an.
Was Sie in diesem Artikel erfahren:
- Welche PHP-Versionen als korrigiert gelten (8.2.35, 8.3.36, 8.4.27, 8.5.12)
- Das vollständige Read-only-Skript zum Kopieren
- Wie Sie GRUEN, GELB und ROT richtig lesen
- Was das Skript bewusst nicht prüft – und was Sie danach tun
Betrifft PHP2Shell meinen Server in Kärnten?
Betroffen ist jeder Server, auf dem eine PHP-Version unterhalb des korrigierten Stands läuft – egal ob als Kommandozeilen-Programm, als PHP-FPM (der Prozessmanager hinter den meisten Webseiten) oder als CGI-Variante. Die Lücken liegen im Interpreter selbst, nicht in einem einzelnen CMS-Plugin. Auch ein sauber gewartetes WordPress hilft also nichts, wenn die PHP-Laufzeit darunter veraltet ist.
Das Spannungsfeld heißt Komfort gegen Überblick: Mehrere PHP-Versionen nebeneinander sind praktisch, weil jede Kundenwebseite ihre eigene Laufzeit bekommt. Sie vervielfachen aber die Zahl der Stellen, die gepatcht werden müssen. Im Projektalltag in Kärnten sehen wir Server, auf denen neben dem System-PHP noch drei weitere Versionen laufen, etwa bei Plesk, wie in unserem Beitrag zum Plesk-Server für No-Code-Tools beschrieben.
Als korrigiert gelten laut PHP-Sicherheitshinweis vom 21. Oktober 2026 mindestens diese Versionen:
| Versionszweig | Mindestversion |
|---|---|
| PHP 8.2 | 8.2.35 |
| PHP 8.3 | 8.3.36 |
| PHP 8.4 | 8.4.27 |
| PHP 8.5 | 8.5.12 |
Ältere Zweige wie PHP 8.1, 8.0, 7.x oder 5.x erhalten vom PHP-Projekt keinen regulären Support mehr. Hier ist die Migration das eigentliche Thema, nicht nur dieser eine Patch. Die Details stehen im offiziellen PHP-Sicherheitshinweis.
Wenn Sie wissen möchten, wie sich solche Lücken in Ihrer Infrastruktur dauerhaft im Griff behalten lassen, helfen wir weiter: IT-Sicherheit für Unternehmen in Kärnten
Wie prüfe ich meine PHP-Versionen und Prozesse automatisch?
Speichern Sie das folgende Skript als php-sicherheitsreport.sh, machen Sie es mit chmod +x php-sicherheitsreport.sh ausführbar und starten Sie es mit Administratorrechten. Das Skript arbeitet ausschließlich lesend: Es sucht PHP-Programme an den Standardpfaden (inklusive Plesk und Remi), liest deren Version aus und vergleicht sie mit den Mindeststufen. Zusätzlich inspiziert es laufende Prozesse.
Ist die Version zu alt, sucht es im Paket-Changelog (Debian/Ubuntu) nach einem Backport beider CVEs. Nur wenn beide Kennungen im aktuellen Changelog-Eintrag stehen, gilt die Version als abgesichert.
#!/usr/bin/env bash
# Read-only PHP audit. Rules dated 2026-10-22; only the two listed CVEs.
set -u
set -o pipefail
export LC_ALL=C
color=auto
report=''
pause_at_end=0
pause_report() {
if ((pause_at_end)) && [[ -r /dev/tty && -w /dev/tty ]]; then
printf '\nReport beendet. Eingabetaste zum Schliessen ... ' > /dev/tty
read -r _ < /dev/tty || true
fi
}
trap pause_report EXIT
while (($#)); do
case "$1" in
--html) [ "$#" -ge 2 ] || exit 64; report=$2; shift 2 ;;
--color) color=always; shift ;;
--no-color) color=never; shift ;;
--pause) pause_at_end=1; shift ;;
-h|--help)
echo 'Usage: sudo bash php-sicherheitsreport.sh [--html report.html] [--color|--no-color] [--pause]'
echo 'Exit: 0=no flagged findings, 1=unclear, 2=action required, 64=usage error'; exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 64 ;;
esac
done
green='' red='' yellow='' reset=''
if [[ "$color" == always || ( "$color" == auto && -t 1 ) ]]; then
green=$'\033[1;32m'; red=$'\033[1;31m'; yellow=$'\033[1;33m'; reset=$'\033[0m'
fi
good=0; warn=0; bad=0; html=''; count=0
escape() { local s=$1; s=${s//&/\&}; s=${s//</\<}; s=${s//>/\>}; s=${s//\"/\"}; printf '%s' "$s"; }
row() {
local status=$1 title=$2 detail=$3 tint=$yellow
case "$status" in
GRUEN) tint=$green; good=$((good+1));;
ROT) tint=$red; bad=$((bad+1));;
*) warn=$((warn+1));;
esac
printf '%s[%s]%s %s\n %s\n' "$tint" "$status" "$reset" "$title" "$detail"
html+="<tr class=\"$status\"><td>$status</td><td>$(escape "$title")</td><td>$(escape "$detail")</td></tr>"
}
# Require both CVEs in the CURRENT installed Debian changelog stanza.
# A mention in an older stanza, or a negative/ambiguous entry, is not auto-approved.
backport() {
local binary=$1 owner pkg installed sourcever file stanza first text
command -v dpkg-query >/dev/null 2>&1 || return 1
owner=$(dpkg-query -S "$binary" 2>/dev/null | head -n 1) || return 1
pkg=${owner%%: /*}
[[ -n "$pkg" ]] || return 1
installed=$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null) || return 1
sourcever=$(dpkg-query -W -f='${source:Version}' "$pkg" 2>/dev/null) || return 1
[[ -n "$sourcever" ]] || sourcever=$installed
file="/usr/share/doc/$pkg/changelog.Debian.gz"
[[ -r "$file" ]] || return 1
stanza=$(gzip -cd "$file" 2>/dev/null | awk 'NR>1 && /^[^[:space:]].* \(.*\).*;/ {exit} {print}')
first=${stanza%%$'\n'*}
[[ "$first" == *"($sourcever)"* ]] || return 1
text=$(printf '%s' "$stanza" | tr '[:upper:]' '[:lower:]')
[[ "$text" == *cve-2026-109618* && "$text" == *cve-2026-109619* ]] || return 1
[[ "$text" == *'backported from 8.2.35'* && "$text" == *'avoid state corruption via'* && "$text" == *bailout* ]] || return 1
[[ "$text" != *revert* && "$text" != *unfixed* && "$text" != *'not fixed'* ]] || return 1
printf '%s %s: aktueller Paket-Changelog nennt den Backport beider CVEs.' "$pkg" "$installed"
}
declare -A seen=()
check_binary() {
local binary=$1 real output version branch minimum='' proof
[[ -x "$binary" && ! -d "$binary" ]] || return
real=$(readlink -f "$binary" 2>/dev/null) || return
[[ -n "${seen[$real]+x}" ]] && return
seen[$real]=1; count=$((count+1))
if command -v timeout >/dev/null 2>&1; then
output=$(timeout 10 "$real" -v 2>&1)
else
output=$("$real" -v 2>&1)
fi
version=$(printf '%s\n' "$output" | sed -nE 's/^PHP ([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -n 1)
if [[ -z "$version" ]]; then row GELB "$real" 'Version nicht ermittelbar.'; return; fi
branch=${version%.*}
case "$branch" in
8.2) minimum=8.2.35;; 8.3) minimum=8.3.36;;
8.4) minimum=8.4.27;; 8.5) minimum=8.5.12;;
esac
if [[ "$output" =~ [0-9]+\.[0-9]+\.[0-9]+(RC|alpha|beta|dev) ]]; then
row GELB "$real PHP $version" 'Vorabversion: keine automatische Freigabe.'
elif [[ -n "$minimum" && "$(printf '%s\n%s\n' "$minimum" "$version" | sort -V | head -n 1)" == "$minimum" ]]; then
row GRUEN "$real PHP $version" "Offizieller korrigierter Versionsstand erreicht ($minimum)."
elif proof=$(backport "$real"); then
row GRUEN "$real PHP $version" "$proof"
elif [[ -n "$minimum" ]]; then
row ROT "$real PHP $version" "Unter $minimum; kein automatisch bestaetigter Backport. Anbieter-Patch pruefen oder aktualisieren; kein Beweis einer Verwundbarkeit."
else
row GELB "$real PHP $version" 'Versionsreihe ohne passende Regel / bestaetigten Backport. Anbieter-Nachweis erforderlich.'
fi
if [[ "$branch" == 5.* || "$branch" == 7.* || "$branch" == 8.0 || "$branch" == 8.1 ]]; then
row GELB "Lebenszyklus PHP $branch" 'Kein regulaerer PHP-Projekt-Support. Anbieter-Support nachweisen und Migration planen, auch bei korrigierten Einzel-CVEs.'
fi
}
host=$(hostname 2>/dev/null || printf unknown)
stamp=$(date -Is)
printf 'PHP-SICHERHEITSREPORT | %s | %s\n' "$host" "$stamp"
printf 'Regelstand: 2026-10-22 | CVE-2026-109618 / CVE-2026-109619\n\n'
if [[ -r /etc/os-release ]]; then
os=$(sed -n 's/^PRETTY_NAME=//p' /etc/os-release | tr -d '"')
printf 'System: %s\n\n' "$os"
fi
if ((EUID != 0)); then row GELB Zugriffsrechte 'Ohne root sind Prozess- und Paketpruefungen eventuell unvollstaendig.'; fi
shopt -s nullglob
for binary in /usr/bin/php /usr/bin/php[0-9]* /usr/bin/php-cgi* /usr/sbin/php-fpm* /usr/local/bin/php /usr/local/sbin/php-fpm /opt/plesk/php/*/bin/php /opt/plesk/php/*/bin/php-cgi /opt/plesk/php/*/sbin/php-fpm /opt/remi/php*/root/usr/bin/php /opt/remi/php*/root/usr/sbin/php-fpm; do
check_binary "$binary"
done
# Also discover nonstandard binaries already running on the host.
unreadable=0; relevant=0; stale=0
for proc in /proc/[0-9]*; do
comm=$(cat "$proc/comm" 2>/dev/null) || continue
case "$comm" in php*|apache*|httpd*|lsphp*|frankenphp*) ;; *) continue;; esac
relevant=$((relevant+1))
exe=$(readlink "$proc/exe" 2>/dev/null) || { unreadable=$((unreadable+1)); continue; }
if [[ "$exe" == *' (deleted)' ]]; then
row ROT "Prozess ${proc##*/} ($comm)" "Ersetztes Hauptprogramm: $exe. Dienstneustart pruefen."
stale=$((stale+1))
else
case "$comm" in php*|lsphp*) check_binary "$exe";; esac
fi
if [[ -r "$proc/maps" ]]; then
maps=$(awk '/\(deleted\)/ && /\.so/ {line=$0; sub(/^([^ ]+ +){5}/,"",line); print line}' "$proc/maps" 2>/dev/null | sort -u)
if [[ -n "$maps" ]]; then
row ROT "Prozess ${proc##*/} ($comm): Bibliotheken" "Ersetzte Bibliothek/Modul geladen: $maps. Dienstneustart pruefen."
stale=$((stale+1))
fi
else unreadable=$((unreadable+1)); fi
case "$comm" in
frankenphp*|lsphp*) row GELB "$comm: Sonderlaufzeit" 'Herstellerspezifische Laufzeit erfordert zusaetzliche Versions-/Patchpruefung.';;
esac
done
if ((count==0)); then row GELB Inventar 'Keine auswertbaren PHP-Programme an den Standardpfaden gefunden.'; fi
if ((unreadable>0)); then row GELB Prozesspruefung "$unreadable Prozesszugriffe nicht moeglich; Ergebnis unvollstaendig."; fi
if ((relevant==0)); then
row GELB Prozesspruefung 'Keine passenden laufenden Host-Prozesse gefunden; keine Aussage zur Web-Laufzeit.'
elif ((stale==0 && unreadable==0)); then
row GRUEN Prozesspruefung 'Keine als deleted markierten Hauptprogramme oder .so-Dateien in geprueften Prozessen. Dies bestaetigt nicht allein deren Patchstand.'
fi
row GELB Pruefumfang 'Keine vollstaendige Serverfreigabe: Website-Zuordnung, Container, eingebettetes PHP und Apache-mod_php-Paketstand sind nicht vollstaendig geprueft. Keine Einbruchserkennung.'
printf '\nZusammenfassung: %s GRUEN | %s GELB | %s ROT\n' "$good" "$warn" "$bad"
printf 'GRUEN gilt nur fuer den jeweiligen Befund. ROT = Handlungsbedarf; GELB = klaeren.\n'
if [[ -n "$report" ]]; then
if (set -o noclobber; cat > "$report" <<HTML
<!doctype html><html lang="de"><meta charset="utf-8"><title>PHP-Sicherheitsreport</title>
<style>body{font:16px system-ui;margin:32px;background:#f5f7fa;color:#18212b}table{border-collapse:collapse;width:100%;background:white}td,th{padding:12px;border:1px solid #ccd3dd;text-align:left;overflow-wrap:anywhere}td:first-child{font-weight:bold}.GRUEN td:first-child{background:#c8f1d5;color:#14532d}.GELB td:first-child{background:#fff0b3;color:#713f12}.ROT td:first-child{background:#ffd0d0;color:#7f1d1d}</style>
<h1>PHP-Sicherheitsreport</h1><p>$(escape "$host") | $(escape "$stamp")</p>
<p>Regelstand 2026-10-22. Nur CVE-2026-109618 und CVE-2026-109619. Keine allgemeine Sicherheitsfreigabe.</p>
<p>$good GRUEN | $warn GELB | $bad ROT</p>
<table><tr><th>Status</th><th>Pruefung</th><th>Ergebnis</th></tr>$html</table>
<p><a href="https://www.php.net/archive/2026.php#2026-10-21-5">Offizieller PHP-Sicherheitshinweis</a></p></html>
HTML
); then printf 'HTML-Report: %s\n' "$report";
else printf 'Report konnte nicht angelegt werden (bestehende Dateien werden nicht ueberschrieben).\n' >&2; exit 2; fi
fi
((bad>0)) && exit 2
((warn>0)) && exit 1
exit 0
Starten Sie es so:
sudo bash php-sicherheitsreport.sh
Für einen zusätzlichen HTML-Report, etwa zur Dokumentation für Ihre Kunden oder eine NIS2-Nachweisführung:
sudo bash php-sicherheitsreport.sh --html /root/php-report.html
Lesen Sie den Code vor der Ausführung auf Ihrem Produktivserver kurz durch – das gilt bei jedem Skript aus dem Internet, auch bei unserem. Der Report-Pfad sollte nicht in einem öffentlich erreichbaren Webverzeichnis liegen, weil der Bericht Pfade und Versionen Ihres Servers verrät.
Wie lese ich GRUEN, GELB und ROT richtig?
Jede Zeile des Reports hat eine eigene Ampel, und GRUEN gilt nur für den jeweiligen Befund, nicht für den ganzen Server. So ordnen Sie die Ergebnisse ein:
| Status | Bedeutung | Was zu tun ist |
|---|---|---|
| GRUEN | Mindestversion erreicht oder Backport beider CVEs im aktuellen Debian-Changelog belegt | Nichts, bei Prozessen aber Patchstand nicht mit „läuft aktuell” verwechseln |
| GELB | Sonderfall: Vorabversion, nicht unterstützter Zweig (8.1 und älter), unbekannte Laufzeit | Klären, Anbieter-Nachweis einholen, Migration planen |
| ROT | Version unter dem Mindeststand ohne bestätigten Backport, oder Prozess nutzt ersetzte Programme | Updaten und Dienste neu starten |
Dabei wird deutlich: Ein „deleted”-Hinweis bei einem laufenden Prozess ist der Klassiker nach Updates. Das Paket ist bereits ersetzt, aber PHP-FPM oder Apache laufen noch mit dem alten Stand im Arbeitsspeicher. Erst ein Neustart schließt die Lücke wirklich.
Auf unserem eigenen Plesk-Server (Ubuntu 22.04.5 LTS) lieferte das Skript für PHP 8.2.35 und 8.3.36 sowie deren CGI- und FPM-Varianten ausschließlich GRUEN, dazu eine GELB-Zeile, die jeder Lauf ausgibt – dazu gleich mehr.
Was prüft das Skript nicht – und warum ist das wichtig?
Hier liegt der Unterschied zwischen einem grünen Report und einem sicheren Server. Das Skript sagt Ihnen, ob Programme und Prozesse den erwarteten Stand haben. Es sagt Ihnen nicht, dass Ihr Server sicher oder unversehrt ist. Genau deshalb gibt jeder Lauf am Ende die GELB-Zeile „Prüfumfang” aus: Website-Zuordnung, Container, eingebettetes PHP und der Paketstand von Apache mod_php sind nicht vollständig abgedeckt. Eine Einbruchserkennung ist es ebenfalls nicht.
Wichtige Grenzen im Überblick:
- Backport-Prüfung nur für Debian-Familie: Sie nutzt
dpkg-queryund das Debian-Changelog. Auf RPM-Systemen (AlmaLinux, Rocky, RHEL) kann ein tatsächlich gepatchtes Paket mit Backport als ROT erscheinen. Das Skript weist selbst darauf hin: „kein Beweis einer Verwundbarkeit”. Prüfen Sie dann den Herstellerhinweis. - Container und Images: PHP in Docker-Containern oder in eingebetteten Anwendungen sieht das Skript nicht zuverlässig. Dort müssen Sie die Image-Version separat kontrollieren.
- Ohne root unvollständig: Prozess- und Paketprüfungen brauchen Administratorrechte, sonst meldet das Skript eine GELB-Warnung.
- Kein Ersatz für Kompromittierungsprüfung: Wenn ein Server bereits vor dem Patch angreifbar war, beweist ein grüner Report nicht, dass nichts passiert ist.
Die Praxis zeigt jedoch: Gerade diese ehrliche Abgrenzung macht den Report belastbar. Ein Tool, das ohne Einschränkung „alles sicher” meldet, ist für Entscheider gefährlicher als eines, das offene Punkte benennt.
Was tun bei ROT: Update, Neustart, Nachkontrolle?
Wenn das Skript ROT meldet, gehen Sie in dieser Reihenfolge vor:
- Paketquellen aktualisieren und PHP updaten (bei Plesk über den Plesk-Updater, sonst über Ihren Paketmanager).
- PHP-FPM und Webserver neu starten, damit keine Prozesse mit altem Programmstand weiterlaufen.
- Skript erneut ausführen und prüfen, dass alle ROT-Zeilen verschwunden sind.
- GELB-Zeilen klären, insbesondere nicht mehr unterstützte PHP-Zweige.
- Logs auf Auffälligkeiten sichten, wenn der Server vor dem Update öffentlich erreichbar war.
Bei virtuellen Servern lohnt sich vorab ein Snapshot. Wie Sie ein Update-Vorgehen sauber dokumentieren, zeigt unser Beitrag zur Pi-hole-Sicherheitslücke mit Update-Anleitung. Wer einen eigenen Server für WordPress betreibt, findet im Artikel zum WordPress-VPS in Kärnten die Betriebsgrundlagen. Für Unternehmen im NIS2-Umfeld ist die Patch-Dokumentation außerdem Teil der Nachweispflichten, siehe NIS2 für KMU in Kärnten.
Fazit: Patchstand prüfen, Dienste neu starten, Lücken dokumentieren
Geschwindigkeit gegen Gründlichkeit ist hier der falsche Gegensatz: Das Skript liefert den Überblick in Sekunden, die Gründlichkeit kommt danach. Zwei Lücken mit CVSS 9,2 und möglicher RCE ohne Anmeldung gehören zu den Fällen, in denen Stunden zählen. Das Audit nimmt Ihnen die Inventur ab, ersetzt aber weder das Update noch den Neustart noch die Kontrolle auf Einbruchsspuren.
Die Reihenfolge ist klar: Mindestversionen erreichen, alle Dienste neu starten, Report erneut laufen lassen und GELB-Befunde klären. Wir ordnen ein, was ein grüner Report trägt und was nur auf dem Papier gut klingt. Nehmen Sie jetzt Kontakt auf – wir analysieren Ihre Situation und zeigen den nächsten Schritt.
Dieser Beitrag richtet sich an Server-Administrator:innen, IT-Verantwortliche und Geschäftsführer:innen in Kärnten, die ihre PHP-Umgebung nicht dem Zufall überlassen wollen. Bei laufender Betreuung ist das Teil unseres Supports.
Häufige Fragen
Wie prüfe ich, ob mein Server von den PHP-Lücken CVE-2026-109618 und CVE-2026-109619 betroffen ist?
Entscheidend ist die installierte PHP-Version pro Versionszweig: Mindeststand sind PHP 8.2.35, 8.3.36, 8.4.27 und 8.5.12. Mit php -v sehen Sie die Version, das Audit-Skript in diesem Artikel prüft zusätzlich alle PHP-Programme und laufenden Prozesse automatisch.
Reicht ein Paket-Update, oder muss ich PHP-FPM und den Webserver neu starten?
Ein Neustart ist nötig. Laufende Prozesse behalten die alte, verwundbare Programmdatei im Speicher, bis der Dienst neu gestartet wird. Das Skript meldet solche Prozesse als ROT mit dem Hinweis auf ersetzte Programme oder Bibliotheken.
Ist das Skript sicher, darf ich es auf einem Produktivserver ausführen?
Das Skript ist rein lesend: Es ruft php -v auf, liest Paket-Changelogs und die Prozessliste und ändert nichts am System. Nur bei der Option --html schreibt es einen Report, und auch dann überschreibt es keine bestehende Datei. Lesen Sie den Code dennoch vor dem Start durch.
Wir betreiben PHP für Kunden-Webseiten in Kärnten und wissen nicht, wo überall PHP läuft. Wer hilft?
Basic4web inventarisiert PHP-Laufzeiten auf Ihren Servern, spielt die Updates ein und prüft nach dem Neustart, ob alles wieder sauber läuft. Das gilt auch für Plesk-Server mit mehreren PHP-Versionen. Eine unverbindliche Erstberatung ist kostenlos.
Mag.a Carmen Hedenig MAS
Basic4web Kärnten
IT-Expertin für KMU – Internet, IT-Sicherheit, KI & Smart Home.