Security Blog
Posted By Gregory

Under the Hood of WP Cerber 9.9


English version: Under the Hood of WP Cerber 9.9


Dieses Release konzentriert sich hauptsächlich auf die Funktionen von WP Cerber, die man erst bemerkt, wenn etwas schiefgeht. Wir haben die Speicherung und Wiederherstellung von Einstellungen, das Lesen von verschleiertem JavaScript durch den Traffic Inspector und das Verhalten des Plugins auf älteren Hosting-Plattformen optimiert. Außerdem haben wir die langjährige Migration von der WordPress Settings API abgeschlossen. Hier erfahren Sie, was sich im Hintergrund geändert hat und was das für Ihre Websites bedeutet.

Einstellungen, die eine beschädigte Datenbank überstehen

WP Cerber speichert seine Konfiguration in einer einzigen Option, CERBER_CONFIG . Bisher übergab das Plugin, falls dieser Wert beschädigt war und nicht mehr deserialisiert werden konnte, das fehlerhafte Ergebnis direkt an array_merge() . Unter PHP 8 führte dies zu einem fatalen TypeError beim Laden des Plugins. Da der Fehler beim Laden auftrat, war die gesamte Website nicht nur die Administrationsoberfläche des Plugins, sondern die gesamte Website nicht mehr erreichbar.

crb_get_settings() prüft nun den Rückgabewert von crb_unserialize() bevor er verwendet wird. Können die gespeicherten Daten nicht in ein Array geparst werden, greift das Plugin auf seine Standardeinstellungen zurück, anstatt abzustürzen. Der Fehler wird als persistentes kritisches Problem unter dem Code corrupted_settings in CRB_Issues::add() protokolliert. Dieses Problem wird beim nächsten Speichern der Einstellungen automatisch behoben, welches in cerber_settings_update() ausgeführt wird.

Wir haben sorgfältig darauf geachtet, zwei Fälle zu unterscheiden, die zwar ähnlich aussehen, aber nicht identisch sind. Ein leerer oder ungültiger gespeicherter Wert bedeutet lediglich, dass die Einstellungen noch nicht existieren. Dieser Wert wird nie deserialisiert, und das Plugin greift stillschweigend auf die Standardwerte zurück, ohne eine Fehlermeldung auszugeben. Eine Meldung wäre ein Fehlalarm. Das Problem corrupted_settings tritt nun nur noch auf, wenn die Deserialisierung eines nicht leeren gespeicherten Werts in ein Array fehlschlägt – genau die Bedingung, die zum ursprünglichen Absturz in der Produktionsumgebung geführt hat.

Zwei benachbarte Zweige derselben Funktion wurden einheitlich angepasst. Die Kompatibilitätszusammenführung für Cloudflare-Add-ons erfordert nun ein Array vor dem Zusammenführen. Der Zweig CERBER_WP_OPTIONS gibt immer ein Array zurück, wenn keine spezifische Einstellung angefordert wird.

Eine selbstheilende Sicherung zusätzlich zum Schutz

Das Zurücksetzen auf die Standardeinstellungen gewährleistet zwar den Betrieb der Website, verwirft aber gleichzeitig die Konfiguration des Administrators. Daher haben wir eine zusätzliche Wiederherstellungsebene eingeführt. Eine neue Klasse, CRB_Settings_Backup , speichert eine zuletzt gültige Kopie von CERBER_CONFIG im eigenen Key-Value-Speicher des Plugins.

Die Sicherung wird als unformatiertes JSON zusammen mit der Benutzer-ID des Erstellers, einem Zeitstempel und dem Kontext, in dem sie erstellt wurde, gespeichert. Sie wird nach einer erfolgreichen Aktualisierung der Einstellungen, nach einem Import der Einstellungen, nach einem Plugin-Upgrade und durch die tägliche Wartung aktualisiert. Nur eine gültige Konfiguration und ein bekannter Kontextcode dürfen eine bestehende Sicherung ersetzen.

Wenn der Fehlerdetektor eine unlesbare CERBER_CONFIG findet, wird die Wiederherstellung automatisch durchgeführt. Nach erfolgreicher Wiederherstellung zeigt das Plugin eine ausblendbare Warnung an, die den Vorfall erklärt und Sie auffordert, Ihre Einstellungen zu überprüfen und zu speichern. Diese Warnung verschwindet, sobald Sie dies getan haben. Falls keine brauchbare Sicherung vorhanden ist oder der wiederhergestellte Wert nicht geschrieben werden kann, behält das Plugin sein bisheriges Verhalten bei und greift auf die Standardeinstellungen zurück. In diesem Fall wird anstelle der Warnung ein kritischer Fehler gemeldet.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.

Eine korrekte Wiederherstellung erforderte die Berücksichtigung des WordPress-Cachings. recover() löscht die beschädigte CERBER_CONFIG -Option, bevor der wiederhergestellte Wert geschrieben wird. Ohne diesen Schritt könnte ein veralteter Optionscache dazu führen, dass update_site_option() den Wert als unverändert behandelt und den Datenbankeintrag überspringt. Das Backup selbst wird unter Umgehung des Objektcaches gelesen und geschrieben, sodass nur dem persistenten cerber_sets -Eintrag vertraut wird. Die Menge der akzeptierten Kontextcodes ist festgelegt. Der Writer, sync() und der Payload-Validator akzeptieren nur die vier deklarierten Codes, sodass jede gespeicherte Payload dieselbe Validierung besteht, die später von der Wiederherstellung durchgeführt wird.

Keine schwerwiegenden Fehler mehr auf Hosts ohne mysqlnd

WP Cerber liest Daten aus der Datenbank über mysqli. Ein Abrufpfad, CRB_Database::fetch_result_set() , nutzte mysqli_result::fetch_all() , um ein komplettes Resultset mit einem einzigen Aufruf abzurufen. Diese Methode ist nur verfügbar, wenn die PHP-Erweiterung `mysqli` auf dem Treiber `mysqlnd` basiert. Auf Systemen, auf denen `mysqli` mit der älteren Bibliothek `libmysqlclient` kompiliert wurde, fehlt die Methode, und ihr Aufruf führt zu einem schwerwiegenden Fehler.

Die Korrektur folgt einem bereits in cerber-common.php verwendeten Muster. Bevor der schnelle Pfad genutzt wird, prüft der Code nun function_exists('mysqli_fetch_all') . Ist diese Funktion nicht verfügbar, liest er das Ergebnis-Set zeilenweise mit mysqli_result::fetch_array() , sowohl für die Datentypen MYSQLI_ASSOC als auch MYSQLI_NUM . Zeilenreihenfolge, Ergebnisstruktur, Bereinigung und der bestehende Revalt Rückgabevertrag bleiben erhalten. Der Fallback liefert dieselben Daten wie der schnelle Pfad.

Wir haben Administratoren außerdem eine Möglichkeit gegeben, zu erkennen, wann der Fallback-Modus aktiv ist. Ein neuer Detektor in CRB_Issue_Monitor meldet ein Problem mit dem Namen db_driver_no_mysqlnd , wenn die mysqli-Erweiterung geladen ist mysqli_fetch_all() jedoch fehlt. Die Meldung erscheint im Widget „Systembereitschaft“. Sie dient lediglich der Information und bestätigt, dass das Plugin auch im Fallback-Modus weiterhin funktioniert. Zudem wird empfohlen, mysqlnd für bessere Kompatibilität und Leistung zu aktivieren.

Präzisere Erkennung von verschleiertem JavaScript

CRB_JS_Detector untersucht Anfragefelder im Rahmen des standardmäßig aktivierten Traffic Inspectors. Er sucht nach verschleiertem JavaScript-Code, der primitive Funktionen wie eval , script und XMLHttpRequest verbirgt. Ziel ist eine zuverlässige Erkennung mit geringer Fehlalarmrate. Der Detektor dekodiert ausschließlich eindeutig kodierte Zeichenketten und ignoriert alle anderen Eingaben. Diese Version enthält mehrere Änderungen, die sowohl eine Sicherheitslücke schließen als auch den Erfassungsbereich des Detektors erweitern.

Ein Fehler, der vollständig hexadezimal maskierte Zeichenketten durchließ.

Das Release enthielt einen Bugfix. Die Hex-Escape-Heuristik normalisierte jede gefundene Zeichenkette mit trim( $m, "\\'\"" ) . PHP's trim() interpretiert das zweite Argument als Zeichenkette zum Entfernen, nicht als Präfix. Der Backslash in dieser Zeichenkette entfernte den führenden Backslash der ersten \xNN -Escape-Sequenz sowie das öffnende Anführungszeichen. Der verbleibende String hatte die ungerade Länge 2N + 1 Ein Prüfkriterium für ungerade Längen entschied daraufhin, dass die Zeichenkette nicht korrekt hexadezimal kodiert war, und übersprang die Dekodierung. Dies führte dazu, dass Zeichenketten, die ausschließlich aus \xNN Escape-Sequenzen bestanden, nicht erkannt wurden und hexadezimal kodierte Funktionen wie eval , script und XMLHttpRequest im Standard-Anfragefeldpfad unentdeckt blieben. Der Fix stellt die korrekte Dekodierung vollständig hexadezimal kodierter Zeichenketten wieder her.

Erweiterte Abdeckung von Escape-Sequenzen und Zeichencodes

Wir haben den Erkennungsumfang des Detektors erweitert. Die Escape-Heuristik erkannte bisher nur \xNN . Sie verarbeitet nun auch \uNNNN und \u{...} Escapes, einschließlich Zeichenketten, die diese Formate mischen, wobei die Regel beibehalten wird, dass nur vollständig maskierte Zeichenketten geprüft werden. Die Dekodierung wurde in eine separate Hilfsfunktion, cerber_decode_js_escapes() , ausgelagert, die ASCII-Codepunkte dekodiert und alle anderen Informationen beibehält. Tritt ein interner PCRE-Fehler auf, gibt diese Hilfsfunktion die ursprüngliche Eingabe anstelle einer leeren Zeichenkette zurück, sodass ein Dekodierungsfehler den zu prüfenden Wert nicht löschen kann.

Die Zeichencode-Heuristik wurde ebenfalls geändert. Bisher wurden dekodierte Ausgaben nur auf externe URLs und IP-Adressen geprüft. Nun werden auch Ausführungs- und DOM-Primitive berücksichtigt. Zahlen werden nur noch über eine explizite fromCharCode(...) `-Konstruktion dekodiert, nicht mehr über beliebige numerische Arrays. Beide Heuristiken verwenden nun ein gemeinsames Token-basiertes Primitivmuster mit Bezeichnergrenzen. Dadurch wird verhindert, dass gewöhnliche Wörter wie description und evaluation mit den darin enthaltenen Teilstrings eval oder script übereinstimmen.

Umschlossene Ganzzahlliterale

fromCharCode ist so definiert, dass sie `ToUint16` auf jedes Argument anwendet. Das bedeutet, dass ein ASCII-Code plus ein Vielfaches von 65536 dasselbe Zeichen ergibt. Eine frühere Version der Heuristik akzeptierte nur Literale bis zu sechs Ziffern, sodass ein siebenstelliger, umgebrochener Wert durchrutschte. Beispielsweise wird 1048677 zur Codeeinheit 101, dem Buchstaben e dekodiert. Der Detektor akzeptiert nun vorzeichenlose Dezimal- und Hexadezimalliterale bis zu Number.MAX_SAFE_INTEGER und reduziert jedes unabhängig von der PHP-Integergröße auf seine `ToUint16`-Codeeinheit. Er lehnt Dezimalzahlen mit führenden Nullen ab, da diese mit dem älteren Oktalsystem mehrdeutig sind. Er begrenzt den Rechenaufwand pro Ziffer, sodass ein sehr langes Literal in einer öffentlichen Anfrage keine unbegrenzte Berechnung auslösen kann.

Eine Folgeuntersuchung schloss eine damit zusammenhängende Lücke. Die Rückgabe eines leeren Strings beim ersten nicht unterstützten Argument führte zum Abbruch des gesamten dekodierten Aufrufs. Ein Angreifer konnte ein einzelnes ungültiges Token an eine ansonsten erkennbare Nutzlast anhängen und so die Erkennung verhindern. Ein solches Token ist 9007199254741024 , das JavaScript mittels ToUint16 einem Leerzeichen zuordnet. Nun wird ein strukturell gültiges, aber nicht unterstütztes Token durch einen Unterstrich ersetzt, und der Rest des Aufrufs wird weiterhin dekodiert. Da der Unterstrich ein Wortzeichen ist, verhindert er außerdem, dass neben einem Schlüsselwort eine künstliche Bezeichnergrenze gebildet wird.

durch Kommentare getrennte Argumente

Die letzte Klasse von Bypass-Methoden nutzte JavaScript-Kommentare. Die Heuristik komprimierte die Eingabe durch Entfernen von Leerzeichen, wobei Kommentare erhalten blieben. Ein zwischen numerischen Argumenten eingefügter Kommentar führte zu einem Fehler in der numerischen Liste, sodass ein Aufruf wie String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) nicht erkannt wurde. Der Detektor entfernt nun Block- und Zeilenkommentare aus der Argumentliste fromCharCode , bevor er sie validiert und dekodiert, wobei der Inhalt von Zeichenketten in Anführungszeichen erhalten bleibt. Die Erfassung läuft außerdem im `dotall`-Modus, sodass eine Argumentliste, die sich über mehrere Zeilen erstreckt, als ein einziger Aufruf gelesen wird.

Die WordPress Settings API wird eingestellt

Die Einstellungsseiten von WP Cerber basierten auf der WordPress Settings API. Formulare wurden an /wp-admin/options.php gesendet, und das Plugin nutzte register_setting() , add_settings_section() , add_settings_field() , settings_fields() ` und do_settings_sections() , um diese zu registrieren und darzustellen. Das funktionierte zwar, band die Admin-Oberfläche des Plugins aber an ein prozedurales WordPress-Subsystem und dessen Konventionen. Mit dieser Version ist die Umstellung auf eine Plugin-eigene Formular-Engine abgeschlossen. Wir haben dies schrittweise und nicht als einmalige Änderung umgesetzt.

Zunächst wurde eine Abgrenzung gezogen. Alle direkten Aufrufe der Settings-API wurden in eine statische Klasse, CRB_Legacy_Settings_Manager ausgelagert. Eine Analyse des Quellcodes ergab, dass nur fünf der zehn Funktionen der Settings-API an sechs Aufrufstellen in zwei Dateien verwendet wurden. Die übrigen fünf Funktionen wurden nie aufgerufen, daher enthält die Abgrenzungsklasse bewusst keine Methoden für sie. Dadurch blieb das Verhalten exakt erhalten. Optionsnamen, Optionsgruppen, Abschnitts- und Feld-IDs, Rückruffunktionen, Hook-Timing und Ausgabe blieben unverändert.

Anschließend haben wir den Ersatz entwickelt. Eine neue Klasse, CRB_Settings_Renderer , rendert Abschnitte und Feldzeilen direkt aus der deklarativen Konfiguration, die von cerber_settings_config() zurückgegeben wird. Einstellungsformulare werden nun an die Admin-Seite des Plugins anstatt an options.php gesendet. Die Übermittlungen werden von der bestehenden Pipeline bei admin_init verarbeitet und anschließend per POST-Redirect-GET zurück zur Einstellungsseite geleitet. Das gerenderte Markup ist byte-identisch mit dem, was zuvor do_settings_sections() erzeugt wurde, bis hin zu Überschriften, Abschnittsblöcken und Formulartabellenzeilen.

Die Nonce-Verifizierung wurde entsprechend angepasst. Anstatt check_admin_referer() gegen eine Optionsgruppe der Settings API auszuwerten, überprüft das Plugin nun sein eigenes cerber_nonce Feld. Dieses Feld war bereits in jedem Einstellungsformular vorhanden, auch in Formularen älterer Versionen. Eine ungültige oder abgelaufene Nonce stoppt die Anfrage nicht mehr mit wp_die() . Stattdessen wird eine Administratorbenachrichtigung in die Warteschlange gestellt und Sie werden zum Formular zurückgeleitet.

Durch die Entfernung options.php wurde auch ein unauffälliges Altverhalten beseitigt. Der alte Pfad schrieb eine Rohkopie jeder Formulargruppe in eine gruppenspezifische Option namens cerber-{group} . Diese Kopien wurden nur bei der Migration vor Version 9.3.4 und bei der Deinstallationsbereinigung gelesen, niemals zur Laufzeit. Sie werden nun nicht mehr geschrieben.

Cerber.Hub und verwaltete Websites

Zwei Kontexte behielten den alten Pfad noch eine Weile bei, da beide auf dem Wire-Format der Settings API basierten. Zum einen das Remote-Rendering von Cerber.Hub, bei dem das Protokoll zwischen der Hauptseite und den verwalteten Seiten die Felder option_page und _wpnonce enthielt. Zum anderen der Bearbeitungsbildschirm der verwalteten Seiten, der über einen von options.php ausgelösten pre_update_option Filter speicherte. Wir haben beide nacheinander migriert.

Das Bearbeitungsformular für verwaltete Websites wird nun über CRB_Settings_Renderer gerendert und über die neue Funktion nexus_save_client_data_form() gespeichert. Diese Funktion reproduziert die Datenverarbeitung des alten Callbacks exakt, einschließlich der Gruppenauflösung, der Bereinigung der Besitzerdetails mittels strip_tags und der Löschung ungenutzter Gruppen. Die Sicherheitsgrenze mit expliziten Prüfungen nexus_is_main() und is_super_admin() bleibt unverändert. Der Cerber.Hub-Client hat daraufhin ebenfalls die Emulation der Settings-API entfernt. Anschließend wurden CRB_Legacy_Settings_Manager und mehrere nicht mehr benötigte Hilfsfunktionen gelöscht.

Remote-Einstellungsformulare senden keine versteckten Felder der Settings API mehr aus, wie z. B. option_page , action=update , _wpnonce oder _wp_http_referer . Sie enthalten nun dieselben internen Felder wie lokale Formulare, zusätzlich aber das Nexus-Siegel im Remote-Kontext. Die Nexus-Transportauthentifizierung bleibt unverändert und bildet weiterhin die äußere Grenze für Anfragen an verwaltete Websites. nexus_is_valid_request() , nexus_is_granted() und der Roundtrip cerber_nexus_seal funktionieren wie bisher. Eine weitergeleitete Anfrage überprüft weiterhin die Plugin-Nonce, bevor die Verarbeitung der Einstellungen beginnt. Zusätzlich wurde eine Einstiegspunktprüfung hinzugefügt. Eine übermittelte Einstellungsseite, die nicht auf eine bekannte Seite verweist, wird nun mit einem WP_Error geschlossen, bevor die Verarbeitung startet. Die Fehlermeldung enthält den übermittelten Wert zu Diagnosezwecken.

Ein harter Umstieg und eine Sache, die man nach dem Upgrade wissen sollte

Dies war eine bewusste, abrupte Umstellung. Wir haben keinen Übergangspfad für das Dual-Format eingerichtet, und die Verarbeitung options.php entfällt. Diese Entscheidung hat einen Sonderfall zur Folge, der hier explizit erwähnt werden sollte: Wenn ein Einstellungsformular mit der vorherigen Version von WP Cerber gerendert wurde und Sie es nach dem Upgrade absenden, kann es vorkommen, dass das Speichern einmalig fehlschlägt. Öffnen Sie in diesem Fall die Einstellungsseite erneut und senden Sie das Formular erneut ab. Das neu gerenderte Formular enthält dann den neuen internen Vertrag und wird normal gespeichert. Dasselbe gilt für externe Einstellungsformulare auf verwalteten Websites.

Im Zuge der Migration wurde auch die Namensgebung bereinigt. Die überladene group wurde überall dort, wo sie einen Einstellungsbildschirm identifizierte (einschließlich der Konstante, des Werts des versteckten Formularfelds, der Pipeline-Funktionssignaturen und der Konfigurationsschlüssel), in ` settings_screen_id umbenannt. Der Remote-Fehlercode wurde von unknown_settings_group in unknown_settings_screen umbenannt. Für Add-on-Entwickler ist ein Kompatibilitätsdetail relevant: Die Nutzdaten des Ereignisses update_settings enthalten weiterhin den alten group als Alias für settings_screen_id , da cerber_add_handler() öffentlich ist und externe Handler darauf zugreifen können.

Grundlagen der UI Factory

Unter den Admin-Oberflächen rendert WP Cerber HTML über eine interne UI-Factory anstatt über Inline-Markup. Diese Version enthält zwei neue Strukturbausteine. crb_ui_fragment() erzeugt eine gemischte, geordnete Sammlung von Kindelementen ohne umschließendes Tag. crb_ui_element_set() erzeugt eine homogene Sammlung mit einem festgelegten Kindelementtyp. Wir haben den Fluent-Knoten CRB_UI_Fragment_Builder in CRB_UI_Content_Builder umbenannt, um Verwechslungen mit dem neuen Fragment-Knoten zu vermeiden. Der Klassenalias wurde beibehalten, damit bestehender Code weiterhin funktioniert.

Einige Anrufseiten wurden dadurch vereinfacht. crb_ui_message_box() akzeptiert nun einfache Zeichenketten und Zahlen und bettet diese in maskierte Absatzelemente ein, sodass Anrufer diese Absätze nicht mehr manuell erstellen müssen. Die doppelt angezeigte Fehlermeldung beim Abrufen von Daten im Dashboard und im Traffic-Log wurde in eine gemeinsame Hilfsfunktion zusammengeführt. Das Panel für die Umgebungsdiagnose wurde auf den neuen Knoten neu erstellt. Der gerenderte HTML-Code ist in allen Fällen unverändert. Dies bildet die Grundlage für eine rendererneutrale Admin-Oberfläche und ist aktuell noch nicht sichtbar.

Nach dem Upgrade

Eine kurze Checkliste für Administratoren:

  • Wenn ein Einstellungsformular beim ersten Absenden direkt nach dem Update nicht gespeichert wird, öffnen Sie die Einstellungsseite erneut und speichern Sie sie erneut.
  • Falls WP Cerber jemals auf die Standardeinstellungen zurückgreift oder diese aus einem Backup wiederherstellt, werden Sie über eine Administratormeldung benachrichtigt. Überprüfen Sie Ihre Einstellungen und speichern Sie diese, um die Benachrichtigung zu entfernen.
  • Wenn Ihr Host PHP ohne den mysqlnd-Treiber ausführt, suchen Sie nach dem entsprechenden Hinweis im Widget „Systembereitschaft“. Das Plugin funktioniert weiterhin, und der Hinweis erläutert die empfohlene Änderung.

I'm a team lead in Cerber Tech. I'm a software & database architect, WordPress - PHP - SQL - JavaScript developer. I started coding in 1993 on IBM System/370 (yeah, that was amazing days) and today software engineering at Cerber Tech is how I make my living. I've taught to have high standards for myself as well as using them in developing software solutions.

View Comments
There are currently no comments.