Security Blog
Posted By Gregory

Under The Hood 9.8.3


English version: Under The Hood 9.8.3


Dieses Release dient der Wartung und Verbesserung der Sicherheit und ist keine neue Funktionserweiterung. Die meisten Arbeiten fanden in Bereichen statt, die Website-Betreiber selten im Blick haben: die Datenschicht, die Ihre Sicherheitsprotokolle ausliest, der Code zum Verfassen von Benachrichtigungs-E-Mails und die Exportpfade, die Ihnen eine CSV-Datei Ihrer Aktivitätsdatensätze bereitstellen. Das Aussehen des Plugins ändert sich dadurch nicht, aber seine Zuverlässigkeit und Sicherheit werden deutlich verbessert, insbesondere bei hoher Website-Auslastung, großen Protokolldateien oder wenn ein Angreifer einen noch nicht behobenen Sonderfall ausnutzt.

Nachfolgend finden Sie die Implementierungsdetails für Administratoren und Entwickler, die verstehen möchten, was sich tatsächlich geändert hat.

Schließen eines E-Mail-Empfänger-Injektionsvektors

WP Cerber versendet zwei Arten von Transaktions-E-Mails, die den Namen des Empfängers enthalten: die PIN-Nachricht für die Zwei-Faktor-Authentifizierung und die Aktivitätsbenachrichtigung. Beide verwenden das bekannte RFC-5322-Format Name <email> für den Empfänger und übernehmen diesen Namen direkt aus den WordPress-Profilfeldern ( user_firstname , user_lastname und display_name ), ohne ihn zu maskieren.

Das Problem liegt in der Art und Weise, wie wp_mail() einen String-Empfänger verarbeitet. Der String wird an einem Komma getrennt, wobei die Grenzen von Anführungszeichen nicht beachtet werden. Ein Benutzer, der die richtigen Zeichen in seinen Profilnamen einfügt, könnte daher eine zusätzliche Adresse in die Empfängerliste der von CRB_2FA::send_user_pin() erzeugten 2FA-PIN-E-Mail sowie der über den user_list Zweig von cerber_get_email() erzeugten Warn-E-Mail einfügen. Auf einer Website, auf der sich jeder registrieren und einen Anzeigenamen festlegen kann, ist dies ein sicherer Weg, um unbemerkt eine Kopie einer Sicherheitsnachricht weiterzuleiten.

Wir haben in cerber-common.php einen speziellen Bereinigungsmechanismus, crb_sanitize_mail_display_name() , hinzugefügt und ihn an beiden Aufrufstellen angewendet. Dieser entfernt genau die Zeichen, die für diese Art von Einschleusungsversuch relevant sind: Komma, Anführungszeichen, Backslash, spitze Klammern und Steuerzeichen. Der sichtbare Name wird für legitime Benutzer weiterhin korrekt angezeigt, und die Empfängerliste kann nicht mehr durch ein manipuliertes Profilfeld beeinflusst werden.

Entfernen eines gespeicherten XSS-Vektors im Besitzscanner

Der Malware-Scanner meldet Änderungen der Besitzverhältnisse eines installierten Plugins im WordPress.org-Repository. Dazu verwendet er die vom Repository bereitgestellten Metadaten zu den Besitzverhältnissen und hat diese bisher teilweise als reines HTML in die Admin-Benachrichtigung eingebunden, einschließlich manuell erstellter Anker-Tags für Links zum Besitzerprofil.

Dieses Repository ist eine vertrauenswürdige Quelle, und die Wahrscheinlichkeit schädlicher Metadaten ist gering. Dennoch sollten externe Daten niemals als unmaskierte Auszeichnungssprache die Administrationsoberfläche erreichen. Genau diese Annahme, dass es vertrauenswürdig ist, möchten wir vermeiden.

Um den Vektor korrekt zu schließen, anstatt ihn lokal zu patchen, haben wir ein neues UI-Factory-Element namens ` formatted_text eingeführt, das mithilfe der Hilfsfunktion crb_ui_formatted_text() erstellt wird. Es rendert eine Klartextvorlage mit nummerierten Platzhaltern vom Typ %N$s . Literale Fragmente der Vorlage und skalare Argumente werden HTML-kodiert, während jedes Argument, das selbst ein UI-Element ist, über den aktiven Renderer gerendert wird. Jeder dynamische Wert wird somit in dem Kontext maskiert, in dem er tatsächlich ausgegeben wird. Dies ist die einzige zuverlässige Methode, um Fehler im Ausgabekontext zu vermeiden.

Die Eigentumsmeldung in crb_check_ownership() verwendet nun dieses Element, und die Links zum Eigentümerprofil werden mit crb_ui_link() anstelle von verketteten Ankerzeichenfolgen erstellt. Dadurch werden Profil-URLs und Anzeigenamen als URLs bzw. als Text maskiert. Die Übersetzungszeichenfolge bleibt unverändert, sodass Lokalisierungen weiterhin funktionieren. Das gespeicherte XSS-Risiko im Adminbereich ist beseitigt.

Behebung des Filters „Beliebiger Softwarefehler“ im Traffic Inspector

Im Traffic Inspector-Protokoll können Sie über das Formular für die erweiterte Suche mehrere Bedingungen kombinieren. Eine davon ist das Kontrollkästchen „Beliebiger Softwarefehler“. Wenn dieses Kontrollkästchen mit anderen Filtern kombiniert wird, können Anfragen mit einem aufgezeichneten PHP-Fehler in den Ergebnissen erscheinen, selbst wenn sie nicht den anderen von Ihnen angegebenen Bedingungen entsprechen.

Die Ursache lag in der Operatorpriorität der WHERE-Klausel. Der alte Code verkettete die Filterfragmente als Zeichenketten, und die gruppierte Fehlerbedingung war nicht in Klammern gesetzt, sodass ein darin enthaltenes OR die umgebende AND Logik umgehen konnte. Durch die Umstellung der Verkehrsabfrage auf den Abfrage-Builder wird die gruppierte Fehlerbedingung nun korrekt in Klammern gesetzt, und der kombinierte Filter verhält sich wie erwartet. Beim Eingrenzen einer Suche werden nun alle festgelegten Bedingungen berücksichtigt.

Passende Suchplatzhalter

Die erweiterte Suche in den Verkehrsprotokollen ließ bisher ein % oder _ im Suchbegriff als LIKE Metazeichen zu, da der Begriff ohne Maskierung in das Suchmuster eingefügt wurde. Dies ist bestenfalls ein unbeabsichtigtes Merkmal und schlimmstenfalls ein unnötiges Lastmuster.

Jeder LIKE Begriff durchläuft nun CRB_Database::escape_like() bevor die umgebenden Wildcards hinzugefügt werden. Dadurch werden % und _ als die vom Benutzer eingegebenen Zeichen erkannt. Die Suchvorgänge verhalten sich vorhersehbar, und die Abfrage kann nicht dazu verleitet werden, mehr Datenbanken zu durchsuchen als beabsichtigt.

Korrektur der Speicherbegrenzungsbehandlung für Exporte

Der Export großer Aktivitäts- oder Traffic-Logs ist speicherintensiv. Daher erhöht WP Cerber den verfügbaren Speicher vor dem Vorgang. In manchen Umgebungen wurde ein numerischer Speichergrenzwert wie 512 als Byte anstatt als Megabyte interpretiert. In diesem Fall konnte das Plugin den Grenzwert nicht wie vorgesehen erhöhen, und der Exportvorgang wurde möglicherweise vorzeitig abgebrochen.

Der Wert wird nun mit der korrekten Einheit interpretiert, sodass die Speichererhöhung wie vorgesehen erfolgt und große Exporte in den zuvor betroffenen Umgebungen vollständig abgeschlossen werden.

Neuaufbau des Log-Exports im Zusammenhang mit Streaming

Die Aktivitäts- und Datenverkehrsexporte wurden verwendet, um die übereinstimmenden Zeilen in Blöcken zu lesen und für jeden Block dieselbe SELECT Abfrage mit einem wachsenden OFFSET erneut auszuführen. Bei großen Protokolldateien führt dies zu einem Deep-Offset-Scanning, bei dem jeder Block mehr Aufwand verursacht als der vorherige und das Ergebnis im Speicher stetig wächst.

Beide Exporte lesen ihre Zeilen nun in einem einzigen ungepufferten Durchlauf über CRB_Database::query_stream() , eingebettet in einen Generator, der jeweils eine Zeile liefert. Der Speicherverbrauch bleibt konstant, unabhängig von der Anzahl der übereinstimmenden Datensätze, und die Datenbank verarbeitet die Daten nur einmal anstatt pro Datenblock. Da ein ungepufferter Stream die Verbindung während der Verarbeitung sperrt, werden die Zeilenanzahl und der Datumsbereich vorab mit separaten gepufferten COUNT und MIN / MAX -Abfragen, die auf denselben Filtern basieren, ermittelt, bevor der Stream geöffnet wird.

Dem Hilfsprogramm ` crb_file_headers() für gemeinsame Dateidownloads wurden zwei Antwort-Header hinzugefügt. X-Accel-Buffering: no weist Nginx an, beim Vorgehen vor PHP-FPM jeden Datenblock sofort weiterzuleiten, anstatt den gesamten Export vor dem Senden zu puffern. Dies verbessert die Zeit bis zum ersten Byte und verhindert, dass der Proxy eine große CSV-Datei im Speicher hält. Dieser Header ist Nginx-spezifisch und wird von Apache mit `mod_php` sowie von anderen Proxys problemlos ignoriert. Cache-Control: no-store verhindert, dass der Browser oder ein zwischengeschalteter Proxy einen sensiblen Sicherheitslog-Export zwischenspeichert.

Wir haben die Streambereinigung deterministisch gestaltet. Der Export verbraucht den Generator innerhalb eines try / finally -Blocks und gibt den letzten verbleibenden Handle im finally frei. Dadurch wird das ungepufferte Ergebnis freigegeben und die Verbindung bei jedem Abbruchpfad entsperrt: normaler Abschluss, vorzeitiger Abbruch oder eine Ausnahme, die beim Schreiben einer Zeile auftritt. Zuvor konnte ein vorzeitiger Abbruch oder ein Fehler vor vollständiger Verarbeitung dazu führen, dass das Ergebnis offen blieb und die Verbindung gesperrt war, was die nächste Abfrage in dieser Anfrage zum Fehlschlagen brachte.

Meldung des exportierten Datumsbereichs

Bisher wurden im CSV-Exportheader nur die aktiven Filter angezeigt. Sowohl der Aktivitäts- als auch der Verkehrsexport enthalten nun zwei zusätzliche Zeilen im Header, die die Zeitstempel der ältesten und neuesten Datensätze im Exportdatensatz anzeigen. Da der Header vor der ersten Zeile geschrieben wird, stammt der Bereich aus einer zugehörigen MIN / MAX -Abfrage, die auf derselben gefilterten Abfrage wie der Export basiert. Er wird weggelassen, wenn keine Zeilen übereinstimmen. Beim Archivieren eines Exports wird nun der exakte Zeitraum, den er repräsentiert, in der Datei gespeichert.

Exportfehler sichtbar machen, anstatt sie zu verschweigen

Der alte Exportpfad konnte unbemerkt fehlschlagen. Wenn die Datenbank nicht verfügbar war oder der Datenstrom nicht geöffnet werden konnte, erzeugte der vorherige Code in der Regel eine leere CSV-Datei ohne jegliche Erklärung – das schlimmste Ergebnis für jemanden, der während eines Vorfalls Datensätze abrufen wollte.

Die Lese- und Exportpfade beider Protokolle wurden überarbeitet, um ein Revalt Ergebnis zurückzugeben, das entweder die Nutzdaten oder einen strukturierten Fehler mit eindeutigen Codes wie activity_export_query_build_failed , activity_export_db_unavailable und activity_export_stream_failed enthält. Ein Fehler auf einer tieferliegenden Ebene wird in das Ergebnis eingebunden, sodass die ursprüngliche Ursache erhalten bleibt und nicht verworfen wird. Bei Setup-Fehlern wird der Export nun mit wp_die() abgebrochen, bevor auch nur ein einziges CSV-Byte gesendet wird, anstatt eine leere Datei zu streamen.

Schlägt ein Export fehl, sieht ein Administrator mit der Berechtigung manage_options die verkettete Ursache, beispielsweise den zugrunde liegenden Datenbankfehler, in der Fehlermeldung. Benutzer ohne diese Berechtigung sehen diese Information nicht. So erreichen die relevanten Details die zuständigen Personen, während interne Datenbankdetails für alle anderen unzugänglich bleiben. Die Fehlermeldung wird mit crb_escape_html() maskiert.

Zusammenführung der Aktivitäts- und Verkehrsprotokolle in Domänenklassen

Ein Großteil dieses Prozesses war struktureller Natur. Sowohl das Aktivitäts- als auch das Verkehrsprotokoll enthielten SQL-Abfragen und die Ergebnisverarbeitung war über den gesamten Dashboard- und Exportcode verteilt. Wir haben diese Logik in die Klassen CRB_Activity und CRB_Traffic_Log verlagert, sodass die Abfrageerstellung und das Abrufen von Zeilen nun durch klare Methoden wie fetch() und stream_log() erfolgen, anstatt als rohes SQL durch den Präsentationscode zu laufen.

Beide Klassen erstellen ihre Abfragen nun mit dem DB-Warp-Abfragegenerator, der über warp_get_db() abgerufen wird, anstatt WHERE-, JOIN- und LIMIT-Fragmente manuell zu verketten. Dies ist nicht nur aus Gründen der Übersichtlichkeit wichtig. Durch die Weiterleitung aller vom Benutzer angegebenen Filterwerte über eine einzige Escaping-Schicht wird die bisherige Mischung aus manuellem Escaping, $wpdb->prepare() und manueller Verkettung mit Anführungszeichen beseitigt – genau die Art von Inkonsistenz, die Injection-Bugs verschleiert. Kann eine Datenbankschicht mit eingeschränkter Funktionalität keine Abfrage erstellen, greift der Code nun sicher mit einer No-Match-Bedingung, anstatt eine ungefilterte Abfrage auszuführen.

Diese Refaktorierungen sind intern und verändern nicht, was auf den Bildschirmen angezeigt wird, aber sie bilden die Grundlage dafür, dass die oben genannten Sicherheits- und Zuverlässigkeitskorrekturen klein, lokal und überprüfbar sind.

Beim Extrahieren des Warncodes wurden zwei latente Fehler gefunden.

Durch die Verlagerung der Administratorbenachrichtigungsausgabe aus CRB_Activity::log() in eine eigene Klasse CRB_Activity_Alerts traten zwei bereits bestehende Fehler zutage, die durch ein Positionsarray verursacht wurden, dessen numerische Indizes nicht mehr korrekt ausgerichtet waren.

Das erste Problem betraf die Dashboard-Links in den Benachrichtigungs-E-Mails. Durch die Zuordnung von spärlichen Schlüsseln und dichten Werten verschoben sich die Werte, sodass ein Link-Parameter wie „ filter_ip anstelle der beabsichtigten Adresse den Anfang eines IP-Adressbereichs erhalten konnte. Die Zuordnung der Werte zu benannten Schlüsseln korrigierte die Ausrichtung, und die Links in den Benachrichtigungs-E-Mails verweisen nun korrekt.

Der zweite betroffene Suchbegriff betraf die Benutzerübereinstimmung. Der Code rief wp_get_current_user() auf, der selbst für die Benutzer-ID 0 ein WP_User Objekt zurückgibt, sodass die Übereinstimmung die falsche Identität verwendete. Nun wird der Benutzer des Ereignisses mit crb_get_userdata() gesucht, um fehlende Benutzer auszuschließen. Benachrichtigungen, die auf einen Benutzer zutreffen, beziehen sich nun auf den richtigen.

Ein übersichtlicheres Dashboard: Bedienerpriorität bei der Änderungsprüfung

CRB_Activity::is_modified_since() verglich einen Zeitstempel mit $stamp < $status['data_modified'] ?? PHP_INT_MAX . Aufgrund der Operatorpriorität wurde dies als (... < ...) ?? PHP_INT_MAX interpretiert, wodurch der Teil mit der Nullwert-Koaleszenz zu totem Code wurde und eine Fehlermeldung wegen eines undefinierten Schlüssels ausgegeben wurde, sobald data_modified fehlte. Die Koaleszenz ist nun in Klammern gesetzt, sodass ein fehlender Änderungsstempel als „geändert“ behandelt wird, analog zur Methode ` is_modified() , und die fälschliche Fehlermeldung verschwindet.

Zentralisierung der Datenbankschema-Definitionen

Der letzte strukturelle Baustein ist die Schema-Wartung. Der Installations- und Upgrade-Code verwendete Inline- CREATE TABLE -SQL für die Protokolltabellen, wodurch dieselbe Tabelle an mehreren Stellen beschrieben werden konnte. Diese Deklarationen stammen nun aus einer einzigen Quelle, CRB_Schema_Definitions , sodass Installation und Upgrade sich auf Basis einer kanonischen Definition verhalten und CRB_Schema_Manager Schema-Drift in bestehenden Installationen zuverlässiger erkennen kann. Auch der rohe DROP INDEX SQL-Befehl wurde durch CRB_Schema_Manager::drop_index_if_exists() ersetzt.

Wir haben außerdem die Dekodierung gespeicherter Anfragefelddaten vereinfacht. Nullbare Legacy-Werte, leere Werte, ungültiges JSON und nicht unterstützte serialisierte Nutzdaten werden nun alle auf dieselbe sichere Weise behandelt, indem ein leeres Array zurückgegeben wird, anstatt dass ein fehlerhafter Datensatz den Lesevorgang unterbricht.

Warum diese Veröffentlichung wichtig ist

Version 9.8.3 enthält keine neuen Schaltflächen. Stattdessen bietet sie eine Datenschicht mit sicherer Fehlerbehandlung anstatt stillschweigender Fehlermeldung, Benachrichtigungs-E-Mails, die nicht durch einen manipulierten Profilnamen gesteuert werden können, eine Administratorbenachrichtigung, die externe Daten korrekt maskiert, Protokollexporte, die im Arbeitsspeicher ausgeführt werden und Sie bei Problemen informieren, sowie Suchfilter, die genau das tun, was im Formular angegeben ist. Solche Änderungen gewährleisten die langfristige Vertrauenswürdigkeit eines Sicherheits-Plugins, und wir ziehen es vor, diese Arbeit transparent zu gestalten, anstatt so zu tun, als sei der vorherige Code bereits perfekt.


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.