hardening-migration.php
Fail-safe-Migration: findet gefährliche Bestandskonfiguration und MELDET sie.
Die Eingabeprüfung dieser Version weist vier Werte ab, die frühere Versionen
durchgelassen haben: die Präfixlänge 0 in einer IP-Liste (0.0.0.0/0,
::/0 — AF-5/CLI-1), einen bypass_cookies-Eintrag ohne Wert (BK-14) und
ein Catch-all-Muster in bypass_ua_allow (BK-9). Dazu kommen zwei Klassen,
die keine Eingabeprüfung abweist, weil sie syntaktisch einwandfrei sind: ein
IP-Bereich in IPv4-mapped Schreibweise (::ffff:0:0/96), der seit der
Kanonisierung aus Befund I2 nichts mehr trifft, und ein UA-Muster wie
Stern-Schrägstrich-Stern, das jeden realistischen Kennzeichner trifft, ohne
ein blosses Sternchen zu sein (B-M2). Das gilt aber alles nur für NEUE
Eingaben. Was bereits in der Option steht, bleibt unangetastet: eine
laufende Installation still umzuschreiben, während der Betreiber nichts
davon weiß, wäre die schlechtere Variante — der Bypass, den er morgen
vermisst, kann der sein, über den sein Monitoring läuft.
Diese Datei ist der Gegenpart dazu. Sie SCHREIBT NICHTS. Sie liest die
Einstellungen, benennt jeden Fund mit Liste, Eintrag und Konsequenz und
zeigt ihn über beide Kanäle: als Admin-Hinweis im Backend und als Zeile in
wp creacaptcha doctor.
Bewusst NICHT gemeldet wird 0.0.0.0/0 in der IP-BLOCKliste: dort heißt der
Eintrag korrekt „alles sperren" und ist zusammen mit der Erlaubnisliste ein
legitimes Whitelist-Only-Muster. Eine Meldung, die auch das anmeckert,
erzieht den Betreiber dazu, Meldungen zu überlesen.
Table of Contents
Constants
- CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG = 'creationell_captcha_hardening_dismiss'
- Query-Parameter des Ausblenden-Links.
- CREATIONELL_CAPTCHA_HARDENING_DISMISS_META = 'creationell_captcha_hardening_dismissed'
- User-Meta-Schlüssel, unter dem ein Benutzer den Hinweis wegklickt.
Functions
- creationell_captcha_hardening_covered_family() : string
- Ermittelt, WELCHE Adressfamilie ein gespeicherter Listeneintrag vollständig abdeckt und die Liste für diese Familie damit als Auswahl aufhebt.
- creationell_captcha_hardening_mapped_ipv4_cidr() : string
- Erkennt einen CIDR-Bereich, der in IPv4-mapped Schreibweise notiert ist (`::ffff:0:0/96`, `::ffff:203.0.113.0/120`), und liefert seine gewöhnliche IPv4-Schreibweise zurück.
- creationell_captcha_hardening_is_catch_all_pattern() : bool
- Prüft, ob ein `bypass_ua_allow`-Muster jeden Browser-Kennzeichner trifft.
- creationell_captcha_hardening_matches_every_user_agent() : bool
- Prüft, ob ein `bypass_ua_allow`-Muster jeden realistischen User-Agent trifft, ohne vom Catch-all-Guard aussortiert zu werden (Befund B-M2).
- creationell_captcha_hardening_scan() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
- Durchsucht einen Satz Einstellungen nach gefährlicher Bestandskonfiguration.
- creationell_captcha_hardening_findings() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
- Führt den Scan gegen die aktuell gespeicherten Einstellungen aus.
- creationell_captcha_hardening_fingerprint() : string
- Kurzkennung des aktuellen Fundbildes.
- creationell_captcha_hardening_dismiss_redirect_target() : string
- Baut das Ziel des Redirects nach dem Ausblenden-Klick: dieselbe Seite ohne den Ausblenden-Parameter und ohne die Nonce.
- creationell_captcha_hardening_handle_dismiss() : void
- Nimmt den Ausblenden-Klick entgegen.
- creationell_captcha_hardening_render_notice() : void
- Zeigt die gefundene Bestandskonfiguration als Admin-Hinweis.
Constants
CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG
Query-Parameter des Ausblenden-Links.
public
mixed
CREATIONELL_CAPTCHA_HARDENING_DISMISS_ARG
= 'creationell_captcha_hardening_dismiss'
CREATIONELL_CAPTCHA_HARDENING_DISMISS_META
User-Meta-Schlüssel, unter dem ein Benutzer den Hinweis wegklickt.
public
mixed
CREATIONELL_CAPTCHA_HARDENING_DISMISS_META
= 'creationell_captcha_hardening_dismissed'
Functions
creationell_captcha_hardening_covered_family()
Ermittelt, WELCHE Adressfamilie ein gespeicherter Listeneintrag vollständig abdeckt und die Liste für diese Familie damit als Auswahl aufhebt.
creationell_captcha_hardening_covered_family(string $entry) : string
Zwei Fragen, zwei vorhandene Wurzeln — hier entsteht bewusst keine dritte IP-Prüfung (die Doppelung eines Validators war in diesem Audit schon zweimal ein Befund, AF-5 und CLI-1):
creationell_captcha_is_valid_ip_or_cidr()— was die Eingabeprüfung dieser Version akzeptiert, ist per Definition kein Altlastwert. Das hält Falschmeldungen auf regulären Einträgen von vornherein fern, auch auf0.0.0.0/1+128.0.0.0/1, mit denen ein Betreiber den gesamten Adressraum weiterhin abdecken darf — wenn er es ausspricht.creationell_captcha_ip_in_cidr()— derselbe Matcher, den Firewall, Proxy-Vertrauen und Watch-Liste zur Laufzeit benutzen. Trifft er die erste UND die letzte Adresse einer Familie, deckt der Eintrag alles dazwischen ebenfalls ab (CIDR-Präfixe sind zusammenhängend).
Deshalb erkennt die Prüfung auch Schreibweisen, die ein Textvergleich gegen
„0.0.0.0/0" verfehlt hätte — 10.0.0.0/0 und 0.0.0.0/00 gehören dazu.
Zurückgegeben wird die Familie und nicht nur ein Ja/Nein, weil die Meldung sie
braucht: 0.0.0.0/0 deckt AUSSCHLIESSLICH IPv4 ab, ::/0 ausschließlich
IPv6. creationell_captcha_ip_in_cidr() verlangt gleiche Binärlänge von
Adresse und Netz, ein Eintrag kann also nie beide Familien treffen — und was
er nicht trifft, läuft an der Liste vorbei. Bei den vertrauenswürdigen
Proxies ist genau das der gefährlichere Teil des Fundes (siehe die Meldung
unten), deshalb darf die Familie hier nicht verlorengehen.
Parameters
- $entry : string
-
Eintrag aus der gespeicherten Liste (bereits getrimmt).
Return values
string —'IPv4', 'IPv6' oder '' — Letzteres heißt: deckt keine Familie vollständig ab, also kein Fund.
creationell_captcha_hardening_mapped_ipv4_cidr()
Erkennt einen CIDR-Bereich, der in IPv4-mapped Schreibweise notiert ist (`::ffff:0:0/96`, `::ffff:203.0.113.0/120`), und liefert seine gewöhnliche IPv4-Schreibweise zurück.
creationell_captcha_hardening_mapped_ipv4_cidr(string $entry) : string
WARUM DAS EINE EIGENE PRÜFUNG IST
Die Prüfung eine Etage höher (creationell_captcha_hardening_covered_family())
kann diese Klasse konstruktionsbedingt nicht finden. Sie steigt bei jedem
Eintrag aus, den creationell_captcha_is_valid_ip_or_cidr() annimmt — und
::ffff:0:0/96 ist eine syntaktisch einwandfreie IPv6-Notation mit
Präfixlänge 96, wird also angenommen. Auch die beiden Familien-Sonden greifen
nicht: 0.0.0.0 ist vier Byte lang, der Eintrag sechzehn, und
creationell_captcha_ip_in_cidr() verlangt zu Recht gleiche Binärlänge.
WAS DER EINTRAG BEDEUTET — GEMESSEN, NICHT VERMUTET
Bis 1.0.2 hat niemand die Client-Adresse vereinheitlicht. Auf einem Server,
dessen PHP die Gegenstelle in mapped Schreibweise sieht (nginx mit
ipv6only=off, Apache auf einem IPv6-Socket, viele Container-Setups), kam
REMOTE_ADDR dort als ::ffff:203.0.113.7 an — sechzehn Byte, dieselbe
Familie wie der Eintrag. ::ffff:0:0/96 traf damit JEDE IPv4-Adresse. In
firewall_trusted_proxies hiess das: jede Gegenstelle gilt als eigener
Proxy, der weitergeleitete Header wird ungeprüft übernommen. Die /0-Regel
der Eingabeprüfung fängt diese Schreibweise nicht.
Seit Modul 27 (Befund I2) kanonisiert creationell_captcha_normalize_ip()
die Client-Adresse auf die gewöhnliche IPv4-Schreibweise — vier Byte. Der
RANGE bleibt bewusst stehen wie geschrieben, weil ein automatisch
umgeschriebenes Vertrauens-Netz die eine Richtung wäre, die hier nie
passieren darf. Folge: der Eintrag trifft heute NICHTS mehr. Gemessen:
ip_in_list( '203.0.113.7', [ '::ffff:0:0/96' ] ) → false
ip_in_list( '::ffff:203.0.113.7', [ '::ffff:0:0/96' ] ) → false
Er ist damit kein offenes Tor mehr, sondern eine Zeile, die für den Betreiber
wie eine wirksame Regel aussieht und keine ist — genau die Klasse unwirksam,
für die dieses Feld existiert. Bei einer Blockliste ist das ein still
verlorener Schutz, bei den Erlaubnis-/Vertrauenslisten eine still verlorene
Ausnahme. Beides gehört gemeldet.
Blanke Einzeladressen in mapped Schreibweise sind NICHT betroffen:
creationell_captcha_ip_in_list() kanonisiert dort beide Seiten, ein
gespeichertes ::ffff:203.0.113.7 trifft also weiterhin. Deshalb verlangt
diese Funktion einen Schrägstrich.
Präfixe unter 96 gelten nicht als mapped Bereich: dort tragen die zwölf
Bytes des Mapped-Präfixes die Entscheidung nicht mehr vollständig, der
Eintrag ist dann ein gewöhnlicher IPv6-Bereich (und ::ffff:0:0/0 fängt
bereits die Familien-Prüfung oben ab).
Parameters
- $entry : string
-
Eintrag aus der gespeicherten Liste (bereits getrimmt).
Return values
string —Gewöhnliche IPv4-Schreibweise des Bereichs, oder '' wenn der Eintrag kein IPv4-Bereich in mapped Notation ist.
creationell_captcha_hardening_is_catch_all_pattern()
Prüft, ob ein `bypass_ua_allow`-Muster jeden Browser-Kennzeichner trifft.
creationell_captcha_hardening_is_catch_all_pattern(string $pattern) : bool
Entschieden wird das vom Produktions-Matcher selbst, nicht von einer zweiten
Regel: creationell_captcha_wildcard_match() kennt den Schalter
$allow_catch_all, und die Bypass-Auswertung ruft ihn mit false auf
(BK-9). Ein Muster, das MIT erlaubtem Catch-all trifft und OHNE nicht mehr,
ist genau eines, das der Schalter aussortiert — was immer die Regel dahinter
gerade ist. Ein Muster wie *pingdom* trifft in beiden Aufrufen gleich und
fällt damit nicht auf.
Ein Catch-all trifft jeden nicht-leeren Kennzeichner, deshalb genügt eine Probe.
GRENZE DIESER PRÜFUNG (Befund B-M2)
Das Kriterium findet definitionsgemäss nur die Menge, die der Laufzeit-Guard
bereits aussortiert — also Muster mit wirkung: unwirksam. Ein Muster, das
jeden realen Kennzeichner trifft, ohne ein blosses Sternchen zu sein, kommt
durch beide Aufrufe gleich zurück und fällt hier NICHT auf. Das genannte
Gegenbeispiel *pingdom* ist harmlos; Stern-Schraegstrich-Stern ist es
nicht — dieses Muster trifft jeden
User-Agent mit einem Schrägstrich, also praktisch jeden. Diese Klasse deckt
creationell_captcha_hardening_matches_every_user_agent() ab.
Nebenwirkung: bei aktivem CREATIONELL_CAPTCHA_DEBUG schreibt der Matcher
eine Zeile ins Log, wenn er ein Catch-all aussortiert — durch diese Prüfung
also auch dann, wenn gerade keine echte Anfrage ausgewertet wird. Die Aussage
der Zeile stimmt trotzdem: das Muster ist wirkungslos.
Parameters
- $pattern : string
-
Muster aus der gespeicherten Liste.
Return values
boolcreationell_captcha_hardening_matches_every_user_agent()
Prüft, ob ein `bypass_ua_allow`-Muster jeden realistischen User-Agent trifft, ohne vom Catch-all-Guard aussortiert zu werden (Befund B-M2).
creationell_captcha_hardening_matches_every_user_agent(string $pattern) : bool
Der Guard in creationell_captcha_wildcard_match() verwirft ein Muster nur,
wenn nach trim( $pattern, '*' ) nichts übrig bleibt — * und ** also,
Stern-Schraegstrich-Stern nicht. Und dieses Muster trifft jeden
User-Agent, der einen Schrägstrich
enthält: Mozilla/5.0 …, curl/8.5.0, python-requests/2.31.0. Wer diese
Zeile einträgt, hat den gesamten Schutz für jeden Besucher abgeschaltet und
bekommt heute keine Meldung.
DAS KRITERIUM IST BEWUSST „ALLE SONDEN" UND NICHT „EINE SONDE"
Gemeldet wird nur, was JEDE der Sonden trifft — also ein Muster, das keinen
realistischen Kennzeichner mehr auslässt. Ein Existenzquantor hätte
Mozilla* (trifft Browser, nicht curl) und *Chrome* mitgemeldet; das
sind enge Auswahlen, keine Totalabschaltungen, und eine Meldung darüber wäre
genau die Falschmeldung, die den Betreiber zum Wegklicken erzieht. Die
Sondenmenge deckt deshalb absichtlich verschiedene Bauformen ab: Browser mit
Klammerausdruck, blankes Werkzeug ohne Leerzeichen, Bibliothek, Bot mit URL.
Anders als beim blossen Sternchen greift hier KEIN Laufzeit-Guard: das Muster
wird ausgewertet und trifft. Der Fund ist deshalb aktiv, nicht unwirksam.
Parameters
- $pattern : string
-
Muster aus der gespeicherten Liste.
Return values
boolcreationell_captcha_hardening_scan()
Durchsucht einen Satz Einstellungen nach gefährlicher Bestandskonfiguration.
creationell_captcha_hardening_scan(array<string, mixed> $settings[, bool|null $gd_available = null ]) : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
Keine Option wird gelesen, keine geschrieben. Der Aufrufer liefert die
Einstellungen; das macht die Erkennung ohne WordPress prüfbar
(tests/test-hardening-migration.php). Der einzige Blick nach draußen ist
extension_loaded( 'gd' ) — und auch der ist über $gd_available
abschaltbar, damit der Test nicht davon abhängt, wie der Rechner gebaut ist,
auf dem er läuft.
Jeder Fund trägt ein Feld wirkung. Drei Werte, weil „greift nicht" drei
verschiedene Dinge heißen kann und der Betreiber sie auseinanderhalten muss:
aktiv— der Eintrag greift zur Laufzeit hier und jetzt.unwirksam— ein Laufzeit-Guard überspringt ihn seit dieser Version dauerhaft (BK-9, BK-14). Kein Schalter holt ihn zurück. Er steht trotzdem in der Konfiguration und liest sich für den Betreiber wie eine wirksame Ausnahme — deshalb genannt.ruhend— der Eintrag greift nur nicht, weil die Funktion, die seine Liste liest, gerade ausgeschaltet ist. Er ist scharf, sobald sie eingeschaltet wird — eine Tretmine, kein toter Buchstabe.
Warum das Feld nicht pauschal aktiv sein darf: zwei der drei IP-Listen
werden zur Laufzeit hinter einem Schalter gelesen (siehe unten). Auf einer
Installation mit den Vorgabewerten (firewall_behind_proxy = false,
code_challenge_enabled = false) bekäme der Betreiber sonst eine rote
Fehlermeldung über zwei Optionswerte, die kein Codepfad überhaupt anschaut —
und lernt daraus, Meldungen dieses Plugins wegzuklicken.
Parameters
- $settings : array<string, mixed>
-
Gespeicherte Plugin-Einstellungen.
- $gd_available : bool|null = null
-
PHP-GD vorhanden?
null= selbst nachsehen. Nur zum Testen gesetzt — GD ist Teil des Watch-Listen-Gates (siehe creationell_captcha_should_issue_code_challenge()), aber eine Umgebungs- und keine Einstellungsfrage.
Return values
array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>creationell_captcha_hardening_findings()
Führt den Scan gegen die aktuell gespeicherten Einstellungen aus.
creationell_captcha_hardening_findings() : array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>
Return values
array<int, array{id: string, liste: string, eintrag: string, wirkung: string, tab: string, meldung: string}>creationell_captcha_hardening_fingerprint()
Kurzkennung des aktuellen Fundbildes.
creationell_captcha_hardening_fingerprint(array<int, array<string, string>> $findings) : string
Ein: id und wirkung. Nicht der Meldungstext — sonst ließe eine
Umformulierung beim nächsten Update einen bereits weggeklickten Hinweis wieder
auftauchen. wirkung muss dagegen einfließen: ein ruhend-Fund wird zu einem
aktiv-Fund, sobald der Betreiber die zugehörige Funktion einschaltet, ohne
dass sich die id ändert. Ohne dieses Feld bliebe die ausgeblendete Warnung
auch dann ausgeblendet, wenn aus der Tretmine ein Loch geworden ist.
Umgekehrt bringt ein NEUER oder geänderter Fund den Hinweis zurück, obwohl der Benutzer den vorigen weggeklickt hat — genau das ist gewollt: ausgeblendet wird ein bestimmtes Fundbild, nicht die Meldung als solche.
Die id geht gehasht ein, nicht roh: sie enthält gespeicherte Listeneinträge
und darf im Zusammenbau kein Trennzeichen zerschießen (ein bypass_cookies-
Eintrag darf jedes Zeichen enthalten, auch Tabulator und Zeilenumbruch).
Parameters
- $findings : array<int, array<string, string>>
-
Fundliste aus dem Scan.
Return values
string —16 Hex-Zeichen, oder '' wenn es nichts zu melden gibt.
creationell_captcha_hardening_dismiss_redirect_target()
Baut das Ziel des Redirects nach dem Ausblenden-Klick: dieselbe Seite ohne den Ausblenden-Parameter und ohne die Nonce.
creationell_captcha_hardening_dismiss_redirect_target() : string
Wirkung des ursprünglichen Fundes war gering und ist es geblieben: Der
Handler hängt hinter current_user_can( 'manage_options' ) und
check_admin_referer(), ein Administrator müsste also selbst eine
//-Schreibweise einer wp-admin-URL aufrufen, und wp_safe_redirect()
fängt einen fremden Host ohnehin ab. Der Ausgang war der von N1 an der
Under-Attack-Stelle beschriebene: statt zurück auf die Seite, von der er
kam, landete der Administrator auf der wp-admin-Startseite — und der
Hinweis, den er gerade weggeklickt hat, wäre dort erneut zu sehen.
Tags
Return values
string —Relativer Ziel-URI für wp_safe_redirect().
creationell_captcha_hardening_handle_dismiss()
Nimmt den Ausblenden-Klick entgegen.
creationell_captcha_hardening_handle_dismiss() : void
Gespeichert wird die serverseitig NEU BERECHNETE Kennung, nicht die aus der URL. Sonst ließe sich mit einem präparierten Link ein Fundbild ausblenden, das es noch gar nicht gibt — der Hinweis wäre schon weggeklickt, bevor er das erste Mal fällig wird.
creationell_captcha_hardening_render_notice()
Zeigt die gefundene Bestandskonfiguration als Admin-Hinweis.
creationell_captcha_hardening_render_notice() : void
Auf allen Admin-Seiten, nicht nur auf den Plugin-Seiten: es geht um einen Schutz, der gerade nicht greift, und wer das erfahren soll, muss dafür nicht erst zufällig die Einstellungsseite öffnen. Damit der Hinweis trotzdem keine Tapete wird, ist er pro Benutzer ausblendbar (und kommt bei einem neuen Fund von selbst zurück, siehe creationell_captcha_hardening_fingerprint()).
Nur für Benutzer mit manage_options — niemand sonst könnte etwas ändern,
und die Meldung nennt Details der Firewall-Konfiguration.