
Detection-Toolkit und reproduzierbares Labor für CVE-2026-87902, einen unauthentifizierten WordPress-Path-Traversal. Enthält Remote-Checker, IoC-Analyzer, Sigma-Regeln und Docker-Testbench.
check/ | Remote-Prüfer, passiv, ohne Serverzugriff |
detect/ | IoC-Analysator + Sigma-Regeln |
offensive/ | Trace-Generator, um die Erkennung an echten Logs zu validieren |
docker-compose.yml + provision/ | Testumgebung, drei Konfigurationen |
tests/validate.py | Qualitätsschranke — Voraussetzung für jede Veröffentlichung |
docs/ANALYSIS.md | Die Schwachstelle, der Fix, die gemessene Erreichbarkeitsanalyse |
make up # montiert das Labor make ioc # Angriffskorpus -> Logs -> Erkennung
make scan # prüft das Labor make test # Qualitätsschranke
Drei WordPress-Instanzen auf 127.0.0.1, die jeden Faktor des Urteils isolieren.
| Port | Instanz | Kern | Aktives Theme | Erwartetes Urteil |
|---|---|---|---|---|
| 8091 | vuln-pre | 6.8.1 — nicht gepatcht | Twenty Twelve, mit page-templates/ | AFFECTED_PRECONDITION_MET |
| 8092 | vuln-nopre | 6.8.1 — nicht gepatcht | Twenty Twenty-Four, ohne page-* | AFFECTED_CORE_ONLY |
| 8093 | patched | 6.8.10 — gepatcht | Twenty Twelve, mit page-templates/ | NOT_AFFECTED |
8092 ist die aufschlussreichste: derselbe verwundbare Kern wie 8091, aber die
Theme-Voraussetzung fehlt. Das zeigt, dass ein Triage, der sich allein auf die Version
stützt, die Exposition überschätzt. Das Theme ist die einzige Variable zwischen 8091
und 8092, der Fix die einzige zwischen 8091 und 8093: alle drei enthalten denselben
Typ von benutzerdefiniertem Inhalt (provision/mu-plugins/00-lab-cpt.php) und dieselbe
Request-Status-Sonde (10-lab-debug.php, Header X-Lab-*, die is_page(), den vom
Loader gesehenen Wert von pagename und das schließlich eingebundene Template
offenlegen).
make up # startet und provisioniert — idempotent, wiederholbar
make status # Version jeder Instanz
make down # Stopp make clean: löscht auch die Volumes
Das offizielle Docker-Image verlinkt /var/log/apache2/access.log auf
/dev/stdout: Die Logs gehen auf die Container-Ausgabe, nicht in eine Datei.
docker compose logs --no-log-prefix vuln-pre # Zugriffe + Fehler
docker compose logs --no-log-prefix vuln-pre > access.log # zur Analyse
docker compose logs -f --no-log-prefix vuln-pre # live
make logs # alle drei Instanzen
Auf einem klassischen Server: /var/log/apache2/access.log, /var/log/nginx/access.log,
oder /home/*/logs/ bei den meisten Shared Hostern. Das Format muss die Query-String
enthalten — %r oder das combined-Format enthalten sie, ein auf %U aufgebautes
LogFormat verliert sie, und ohne sie ist keine Erkennung möglich.
check/wp-ghsa-7hp8-check.pyAus dem Internet, ohne Serverzugriff. Keine Payload, kein Traversal, kein
Schreiben. Für jeden Host: WordPress-Erkennung, Version abgeglichen über fünf Quellen
(meta generator, RSS-Feed, wp-links-opml.php, readme.html, ?ver= der
Kern-Ressourcen), aktives Theme und Sondierung des Verzeichnisses page-*.
python3 check/wp-ghsa-7hp8-check.py --hosts-file hosts.txt --csv out.csv --json out.json
| Urteil | Bedeutung |
|---|---|
AFFECTED_PRECONDITION_MET | Verwundbarer Kern und Verzeichnis page-* im aktiven Theme. Priorität. |
AFFECTED_THEME_UNKNOWN | Verwundbarer Kern, Theme nicht bestimmt. |
AFFECTED_CORE_ONLY | Verwundbarer Kern, Theme-Voraussetzung fehlt. Trotzdem zu patchen. |
VERSION_UNKNOWN | WordPress erkannt, Version verschleiert. |
NOT_AFFECTED | Version ≥ Fix seines Branches. |
„BETROFFEN" bedeutet, dass der verwundbare Code vorhanden ist, nicht dass ein
Angreifer Code ausführen kann. Siehe docs/ANALYSIS.md.
--transport browser (Standard) steuert das installierte Google Chrome; die
Unteranfragen gehen von einem fetch() aus, das in der Seite ausgeführt wird, und
erben deren TLS-Stack, ihre HTTP/2-Header-Reihenfolge und ihre Cookies, was vermeidet,
von einem CDN gefiltert zu werden, bevor die URL gelesen wird. --transport direct
verwendet nur die Standardbibliothek. --scheme http|https vermeidet den Fallback
https → http, der sonst eine 400-Zeile mit einem rohen TLS-ClientHello im Log des
Ziels hinterlässt.
Jede Anfrage wird auf die Millisekunde genau in einem JSONL-Log mit Zeitstempel
versehen: Session-ID, Request-Nummer, Phase, URL, Status, Größe, Dauer, Ausgangs-IP,
Marker. Der Marker SECAUDIT/<nonce> geht als Header X-Security-Audit und als
User-Agent-Suffix mit — hinzugefügt, nie ersetzt, um in einem Standard-Access-Log
sichtbar zu bleiben, ohne die Browser-Signatur zu brechen. Anpassbar über --marker.
Kein Remote-Oracle. Die Option
--probe-inclusionführt einen differentiellen Vergleich auf einem inerten Ziel durch (wp-includes/version.php, bereits beim Bootstrap geladen:require_oncewürde daraus ein vollständiges No-op machen). Auf einer Standardinstallation liefert sieNOT_REACHABLEauch bei einem verwundbaren Kern, mit byte-identischen Antworten zwischen gepatchter und ungepatchter Instanz. Das ist keine Einschränkung des Tools: WordPress antwortet mit 404, bevor es die Template-Hierarchie konsultiert. Zahlenmäßiger Nachweis indocs/ANALYSIS.md, Abschnitt 3.
Eine literale Regex vom Typ pagename=.*%2e%2e%2f lässt sich durch Änderung der
Groß-/Kleinschreibung (%2E), durch eine weitere Kodierung (%252e) oder durch
Mischung von literal und kodiert (templates/..%2f../) umgehen. Jede Musterliste ist
konstruktionsbedingt unvollständig.
Man geht daher vom Code aus, nicht von der Schreibweise des Angreifers:
pagename durchläuft höchstens zwei Dekodierungen, bevor es das Dateisystem
erreicht — die von PHP auf der Query-String, dann das explizite urldecode() von
get_page_template().file_exists() übergebene Pfad
eine ..-Komponente enthalten. Unter Linux wird das übergeordnete Verzeichnis genau
durch die beiden Bytes 0x2E 0x2E geschrieben; es gibt keine andere
Darstellung auf Dateisystemebene.Also: bis zum Fixpunkt dekodieren und auf jeder Ebene testen. Das ist eine strikte Obermenge dessen, was WordPress tut — eine zusätzliche Kodierungsschicht verschiebt die Übereinstimmung nur um eine Ebene, die man ebenfalls durchläuft.
python3 detect/wp-ghsa-7hp8-ioc.py access.log
docker compose logs --no-log-prefix vuln-pre | python3 detect/wp-ghsa-7hp8-ioc.py -
| Regel | Schweregrad | Auslöser |
|---|---|---|
GHSA-7hp8-traversal-pagename | CRITICAL | ..-Komponente in pagename, auf jeder Dekodierungsebene |
GHSA-7hp8-traversal-param | HIGH | dieselbe Primitive in einem anderen Parameter (Themes und Erweiterungen rufen ebenfalls locate_template() auf) |
GHSA-7hp8-traversal-path | HIGH | ..-Komponente im URL-Pfad (nginx lässt %2f durch, Apache standardmäßig nicht) |
GHSA-7hp8-overlong-encoding | MEDIUM | UTF-8-Überkodierung (%c0%ae). Unter Linux wirkungslos, aber nie legitim |
GHSA-7hp8-theme-page-dir-probe | LOW | Sondierung eines Theme-Verzeichnisses page-* — die Reconnaissance |
Sigma-Regeln in detect/sigma-wp-ghsa-7hp8.yml.
Da Sigma nicht rekursiv dekodieren kann, zählen sie die Ebenen 0 bis 3 auf: Das ist
eine bewusste Approximation für das First-Level-Triage. Die Treffer zur Entscheidung
erneut durch den Analysator laufen lassen.
WP::parse_request() liest $_POST vor $_GET. pagename kann daher
in einem Request-Body ankommen, der in keinem Access-Log auftaucht. Erforderliche
Abdeckung auf WAF- oder ModSecurity-Ebene, auf dem Body.Keine Regel auf Access-Logs deckt den ersten Punkt ab. Das ist eine Grenze des Trägers, nicht der Regel — aber sie muss bekannt sein, bevor man vollständige Abdeckung verkündet.
offensive/generate-traces.py spielt 12 verschiedene Schreibweisen derselben
Payload durch (literal, einfach/doppelt/dreifach kodiert, Groß- und Kleinschreibung,
Mischungen, UTF-8-Überkodierung, Punkt-Leerzeichen) plus 7 legitime Anfragen, die
ihnen ähneln (Slug mit Punkten, wp-includes in einem Slug, Datums-Permalink, kodiertes
Prozentzeichen).
Er erzielt nichts: Es existiert kein Remote-Exploit für diesen Vektor auf einer Standardinstallation. Er erzeugt Spuren — das ist sein ganzer Zweck.
make ioc # erzeugt den Korpus, holt die echten Logs, lässt den Analysator laufen
Erwartet und durch make test auf echten Apache-Logs verifiziert:
12 Payloads von 12 erkannt (11 CRITICAL, die Überkodierung als MEDIUM, weil sie
unter Linux nicht ausnutzbar ist), 0 Alarm bei den 7 legitimen Anfragen, und
effektive Erkennung auf drei verschiedenen Dekodierungsebenen.
Ziel beschränkt auf das lokale Labor; jedes andere erfordert --i-have-authorization.
register_argc_argv = Off in der php.ini des Web-SAPI; PEAR deinstallieren, falls
ungenutzt — das ist der im Advisory zitierte Pivot Inklusion → Ausführung.open_basedir auf das Site-Root beschränkt: begrenzt jede lokale Inklusion.make test ist die Veröffentlichungsbedingung: Matrix der 25 Branches des Advisory
(einschließlich Vergleichsfallen — 6.8.9 < 6.8.10 numerisch, Vorversionen, Branches
außerhalb der Matrix), Prüfer gegen die drei Instanzen, und IoC-Regel validiert auf
echten Logs. Die NOT_REACHABLE-Assertion der Sonde ist dort bewusst festgeschrieben:
Wenn sie bricht, hat sich das Verhalten geändert und die Analyse muss überarbeitet
werden.
Nur auf Assets einzusetzen, für die Sie verantwortlich sind, oder unter schriftlichem
Mandat. Das Labor lauscht nur auf 127.0.0.1; die verwundbaren Instanzen dürfen
niemals exponiert werden. Die Sonde 10-lab-debug.php gibt Serverpfade preis: Sie ist
dem Labor vorbehalten.