Under the Hood of WP Cerber 9.9
English version: Under the Hood of WP Cerber 9.9
Ta wersja dotyczy głównie tych elementów WP Cerber, na które nie zwraca się uwagi, dopóki coś nie pójdzie nie tak. Poświęciliśmy ją na udoskonalenie sposobu przechowywania i odzyskiwania ustawień, sposobu odczytywania zaciemnionego kodu JavaScript przez Traffic Inspector oraz zachowania wtyczki na starszych platformach hostingowych. Zakończyliśmy również długotrwałą migrację z API ustawień WordPressa. Oto, co zmieniło się „od podszewki” i co to oznacza dla prowadzonych przez Ciebie witryn.
Ustawienia, które przetrwają uszkodzenie bazy danych
WP Cerber przechowuje swoją konfigurację w pojedynczej opcji CERBER_CONFIG . Do tej pory, jeśli ta wartość uległa uszkodzeniu i nie dało się jej już odserializować, wtyczka przekazywała uszkodzony wynik bezpośrednio do array_merge() . W PHP 8 powodowało to krytyczny błąd TypeError podczas ładowania wtyczki. Ponieważ błąd wystąpił podczas ładowania, powodował on awarię całej witryny, a nie tylko ekranów administracyjnych wtyczki.
crb_get_settings() weryfikuje teraz wartość zwracaną przez funkcję crb_unserialize() przed jej użyciem. Gdy zapisanych danych nie można sparsować do tablicy, wtyczka powraca do ustawień domyślnych zamiast się zawieszać. Rejestruje błąd jako stały problem krytyczny za pomocą CRB_Issues::add() w kodzie corrupted_settings . Problem ten znika automatycznie po następnym zapisaniu ustawień, które jest uruchamiane w cerber_settings_update() .
Starannie oddzieliliśmy dwa przypadki, które wyglądają podobnie, ale nie są. Pusta lub błędnie zapisana wartość oznacza po prostu, że ustawienia jeszcze nie istnieją. Ta wartość nigdy nie jest odserializowana, a wtyczka po cichu wraca do ustawień domyślnych, nie generując żadnych sygnałów. Zgłoszenie tego byłoby fałszywym alarmem. Problem corrupted_settings pojawia się teraz tylko wtedy, gdy niepusta zapisana wartość nie zostanie odserializowana do tablicy, co jest dokładnie przyczyną pierwotnego błędu produkcyjnego.
Dwie pobliskie gałęzie w tej samej funkcji otrzymały tę samą dyscyplinę. Scalenie zgodności dodatku Cloudflare wymaga teraz tablicy przed scaleniem. Gałąź CERBER_WP_OPTIONS zawsze zwraca tablicę, gdy nie jest wymagane żadne konkretne ustawienie.
Samonaprawiająca się kopia zapasowa na szczycie osłony
Powrót do ustawień domyślnych utrzymuje witrynę w działaniu, ale jednocześnie powoduje utratę konfiguracji administratora. Dlatego dodaliśmy warstwę odzyskiwania nad warstwą ochronną. Nowa klasa, CRB_Settings_Backup , przechowuje ostatnią znaną i prawidłową kopię CERBER_CONFIG w pamięci klucz-wartość wtyczki.
Kopia zapasowa jest przechowywana w postaci surowego pliku JSON wraz z identyfikatorem użytkownika, który ją utworzył, znacznikiem czasu i kontekstem, który ją utworzył. Jest odświeżana po pomyślnej aktualizacji ustawień, po imporcie ustawień, po aktualizacji wtyczki oraz w ramach codziennego zadania konserwacyjnego. Tylko prawidłowa konfiguracja i znany kod kontekstu mogą zastąpić istniejącą kopię zapasową.
Gdy detektor uszkodzeń wykryje nieczytelny plik CERBER_CONFIG , odzyskiwanie danych uruchomi się automatycznie. Po pomyślnym przywróceniu wtyczka rejestruje możliwe do odrzucenia ostrzeżenie, które wyjaśnia przyczynę problemu i prosi o sprawdzenie i zapisanie ustawień. Powiadomienie znika po wykonaniu tej czynności. Jeśli nie istnieje użyteczna kopia zapasowa lub nie można zapisać przywróconej wartości, wtyczka zachowuje swoje dotychczasowe zachowanie w trybie awaryjnym i powraca do ustawień domyślnych. W takim przypadku zamiast ostrzeżenia rejestruje problem krytyczny.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.
Prawidłowe odzyskiwanie danych wymagało poszanowania buforowania WordPressa. recover() usuwa uszkodzoną opcję CERBER_CONFIG przed zapisaniem przywróconej wartości. Bez tego kroku nieaktualna pamięć podręczna opcji mogłaby spowodować, że update_site_option() potraktowałaby wartość jako niezmienioną i pominęłaby zapis do bazy danych. Sama kopia zapasowa jest odczytywana i zapisywana z pominięciem pamięci podręcznej obiektów, więc zaufany jest tylko trwały rekord cerber_sets . Zestaw akceptowanych kodów kontekstu jest zamknięty. Funkcja zapisująca, sync() i walidator danych akceptują tylko cztery zadeklarowane kody, więc każdy zapisany ładunek może przejść tę samą walidację, którą później przeprowadza funkcja recovery.
Koniec z krytycznymi błędami na hostach bez mysqlnd
WP Cerber odczytuje dane z bazy danych przez MySQL. Jedna ze ścieżek pobierania, CRB_Database::fetch_result_set() , używała mysqli_result::fetch_all() do pobrania całego zestawu wyników w jednym wywołaniu. Ta metoda istnieje tylko wtedy, gdy rozszerzenie PHP MySQL jest zbudowane na sterowniku MySQL. Na hostach, na których MySQL jest kompilowany ze starszą biblioteką libmysqlclient, metoda ta jest nieobecna, a jej wywołanie powoduje błąd krytyczny.
Poprawka jest zgodna ze schematem już zastosowanym w innym miejscu w cerber-common.php . Przed przejściem na szybką ścieżkę kod sprawdza teraz function_exists('mysqli_fetch_all') . Gdy ta funkcja jest niedostępna, odczytuje wiersz po wierszu zestaw wyników za pomocą mysqli_result::fetch_array() dla kształtów MYSQLI_ASSOC i MYSQLI_NUM . Kolejność wierszy, struktura wyników, czyszczenie i istniejący kontrakt zwrotny Revalt są zachowane. Funkcja zapasowa zwraca te same dane, co w szybkiej ścieżce.
Daliśmy również administratorom możliwość sprawdzenia, czy korzystają z trybu awaryjnego. Nowy detektor w CRB_Issue_Monitor zgłasza problem db_driver_no_mysqlnd , gdy rozszerzenie mysqli jest załadowane, ale brakuje mysqli_fetch_all() . Komunikat pojawia się w widżecie gotowości systemu. Ma on charakter informacyjny. Potwierdza on, że wtyczka nadal działa w trybie awaryjnym i zaleca włączenie mysqlnd w celu zapewnienia lepszej kompatybilności i wydajności.
Dokładniejsze wykrywanie zaciemnionego JavaScript
CRB_JS_Detector inspekcjonuje pola żądań w ramach funkcji Traffic Inspector, która jest domyślnie włączona. Wyszukuje ona kod JavaScript, który został zaciemniony, aby ukryć prymitywy, takie jak eval , script i XMLHttpRequest . Celem projektu jest wysoka skuteczność detekcji i niski wskaźnik fałszywie pozytywnych wyników. Detektor dekoduje tylko dane wejściowe, które są jednoznacznie zakodowanym ciągiem znaków, a wszystkie inne pozostawia bez zmian. Kilka zmian w tej wersji naprawiło rzeczywiste obejście i rozszerzyło zakres odczytu detektora.
Regresja, która pozwala na przejście ciągów znaków w pełni zaszyfrowanych heksadecymalnie
Wydanie rozpoczęło się od poprawki błędu. Heurystyka heksadecymalna normalizowała każdy dopasowany ciąg za pomocą trim( $m, "\\'\"" ) . trim() w PHP traktuje swój drugi argument jako zestaw znaków do usunięcia, a nie jako dosłowny prefiks. Ukośnik odwrotny w tym zestawie usunął początkowy ukośnik odwrotny pierwszego ciągu \xNN wraz z otwierającym cudzysłowem. Pozostała wartość miała nietypową długość 2N + 1 Strażnik nietypową długością uznał, że ciąg nie został poprawnie zakodowany szesnastkowo i całkowicie pominął dekodowanie. W praktyce ciąg zbudowany wyłącznie z ciągu \xNN nie przechodził inspekcji, a prymitywy heksadecymalne, takie jak eval , script i XMLHttpRequest , nie były wykrywane w domyślnej ścieżce pola żądania. Poprawka przywraca poprawne dekodowanie ciągów w pełni zakodowanych szesnastkowo.
Szerszy zakres kodu ucieczki i znaków
Następnie rozszerzyliśmy zakres rozumienia detektora. Heurystyka ucieczki rozpoznawała wcześniej tylko \xNN . Teraz obsługuje ona również ucieczki \uNNNN i \u{...} , w tym ciągi znaków, które mieszają te formaty, zachowując jednocześnie regułę, że sprawdzane są tylko ciągi znaków z pełną przerwą ucieczki. Dekodowanie zostało przeniesione do dedykowanego programu pomocniczego cerber_decode_js_escapes() , który dekoduje punkty kodowe ASCII i zachowuje wszystkie pozostałe. W przypadku wystąpienia wewnętrznego błędu PCRE, program pomocniczy zwraca oryginalne dane wejściowe zamiast pustego ciągu znaków, więc błąd dekodowania nie może usunąć sprawdzanej wartości.
Zmieniła się również heurystyka kodu znaków. Wcześniej analizowała ona zdekodowane dane wyjściowe tylko pod kątem zewnętrznych adresów URL i adresów IP. Teraz analizuje również prymitywy wykonania i DOM. Dekoduje liczby tylko z jawnej konstrukcji fromCharCode(...) a nie z dowolnej napotkanej tablicy liczbowej. Obie heurystyki współdzielą teraz jeden wzorzec prymitywny uwzględniający tokeny z granicami identyfikatorów. Ta zmiana zapobiega dopasowywaniu zwykłych słów, takich jak description i evaluation do podciągów eval lub script w ich obrębie.
Zawinięte literały całkowite
fromCharCode jest zdefiniowany tak, aby zastosować ToUint16 do każdego argumentu. Oznacza to, że kod ASCII plus dowolna wielokrotność 65536 generuje ten sam znak. Wcześniejsza wersja heurystyki akceptowała tylko literały do sześciu cyfr, więc siedmiocyfrowa wartość zawinięta była pomijana. Na przykład, 1048677 dekoduje do jednostki kodowej 101, litery e . Detektor akceptuje teraz literały dziesiętne bez znaku i szesnastkowe do Number.MAX_SAFE_INTEGER i redukuje każdy z nich do jednostki kodowej ToUint16, niezależnie od rozmiaru liczby całkowitej PHP. Odrzuca on zera wiodące, ponieważ są one niejednoznaczne z przestarzałymi systemami ósemkowymi. Ogranicza on pracę na cyfrę, więc bardzo długi literał w żądaniu publicznym nie może prowadzić do nieograniczonych obliczeń.
Kontynuacja wypełniła powiązaną lukę. Zwrócenie pustego ciągu znaków w pierwszym nieobsługiwanym argumencie powodowało przerwanie całego zdekodowanego wywołania. Atakujący mógł dołączyć pojedynczy token spoza zakresu do w innym przypadku wykrywalnego ładunku i zablokować detekcję. Jednym z takich tokenów jest 9007199254741024 , który w JavaScript jest mapowany na spację za pomocą ToUint16. Teraz strukturalnie poprawny, ale nieobsługiwany token jest zastępowany znakiem podkreślenia, a reszta wywołania jest nadal dekodowana. Podkreślenie jest znakiem słowa, więc blokuje również utworzenie sfabrykowanej granicy identyfikatora obok słowa kluczowego.
Argumenty rozdzielone komentarzami
Ostatnia klasa obejścia wykorzystywała komentarze JavaScript. Heurystyka zagęszczała dane wejściowe, usuwając spacje, co pozostawiało komentarze na miejscu. Komentarz wstawiony między argumenty numeryczne przerywał dopasowanie listy numerycznej, więc wywołanie takie jak String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) unikało wykrycia. Detektor usuwa teraz komentarze blokowe i wierszowe z listy argumentów fromCharCode przed jej walidacją i dekodowaniem, zachowując jednocześnie zawartość ciągów znaków w cudzysłowie. Przechwytywanie działa również w trybie dotall, więc lista argumentów obejmująca kilka wierszy jest odczytywana jako pojedyncze wywołanie.
Wycofywanie interfejsu API ustawień WordPressa
Strony ustawień WP Cerber zostały zbudowane w oparciu o API ustawień WordPressa. Formularze były publikowane w /wp-admin/options.php , a wtyczka korzystała z register_setting() , add_settings_section() , add_settings_field() , settings_fields() i do_settings_sections() do ich rejestracji i renderowania. To rozwiązanie działało, ale wiązało interfejs użytkownika administratora wtyczki z proceduralnym podsystemem WordPressa i jego konwencjami. Ta wersja kończy proces przejścia na silnik formularzy należący do wtyczki, a my robiliśmy to etapami, a nie pojedynczo.
Najpierw nakreśliliśmy granicę. Każde bezpośrednie wywołanie API Ustawień zostało przeniesione do jednej klasy statycznej, CRB_Legacy_Settings_Manager . Skanowanie bazy kodu wykazało, że użyto tylko pięć z dziesięciu funkcji API Ustawień, w sześciu miejscach wywołań w dwóch plikach. Pozostałe pięć nie zostało nigdy wywołane, więc klasa graniczna celowo nie zawiera dla nich żadnych metod. Ten krok dokładnie zachował zachowanie. Nazwy opcji, grupy opcji, identyfikatory sekcji i pól, wywołania zwrotne, synchronizacja hooków i dane wyjściowe pozostały niezmienione.
Następnie zbudowaliśmy zamiennik. Nowa klasa, CRB_Settings_Renderer , renderuje sekcje i wiersze pól bezpośrednio z deklaratywnej konfiguracji zwracanej przez cerber_settings_config() . Formularze ustawień teraz przesyłają dane z powrotem na stronę administracyjną wtyczki, a nie do options.php . Zgłoszenia są przetwarzane przez istniejący potok w admin_init , a następnie następują żądania POST-redirect-GET z powrotem na stronę ustawień. Wyrenderowany kod jest identyczny bajtowo z tym, który wcześniej wygenerowała do_settings_sections() , aż do nagłówków, bloków sekcji i wierszy tabeli formularza.
Weryfikacja nonce'ów uległa zmianie. Zamiast check_admin_referer() w odniesieniu do grupy opcji API ustawień, wtyczka weryfikuje teraz własne pole cerber_nonce . To pole było już obecne w każdym formularzu ustawień, w tym w formularzach renderowanych przez starsze wersje. Nieprawidłowy lub wygasły nonce nie zatrzymuje już żądania za pomocą wp_die() . Kolejkuje on powiadomienie administratora i powraca do formularza.
Rezygnacja z options.php usunęła również pewien cichy element starego zachowania. Stara ścieżka zapisywała surową kopię każdej grupy formularzy do opcji dla każdej grupy o nazwie cerber-{group} . Kopie te były odczytywane wyłącznie podczas migracji do wersji wcześniejszej niż 9.3.4 oraz podczas czyszczenia po odinstalowaniu, a nigdy w czasie wykonywania. Nie są już zapisywane.
Cerber.Hub i zarządzane witryny
Dwa konteksty zachowały starą ścieżkę nieco dłużej, ponieważ oba zależały od formatu połączenia API ustawień. Jednym z nich było zdalne renderowanie Cerber.Hub, gdzie protokół między witryną główną a jej witrynami zarządzanymi zawierał pola option_page i _wpnonce . Drugim był ekran edycji witryny zarządzanej, który został zapisany za pomocą filtra pre_update_option uruchamianego przez options.php . Migrowaliśmy każdy z nich po kolei.
Formularz edycji witryny zarządzanej jest teraz renderowany przez CRB_Settings_Renderer i zapisywany za pomocą nowej funkcji nexus_save_client_data_form() . Funkcja ta dokładnie odtwarza obsługę danych starego wywołania zwrotnego, w tym rozpoznawanie grup, oczyszczanie danych właściciela za strip_tags oraz czyszczenie nieużywanych grup. Zachowuje tę samą granicę bezpieczeństwa z jawnym zabezpieczeniem nexus_is_main() i is_super_admin() . Klient Cerber.Hub również porzucił emulację API ustawień. Następnie usunięto CRB_Legacy_Settings_Manager i kilka nieaktywnych funkcji pomocniczych.
Formularze ustawień zdalnych nie emitują już ukrytych pól API ustawień, takich jak option_page , action=update , _wpnonce lub _wp_http_referer . Zawierają teraz te same pola wewnętrzne, co formularze lokalne, a także pieczęć Nexus w kontekście zdalnym. Uwierzytelnianie transportu Nexus pozostaje niezmienione i stanowi zewnętrzną granicę dla żądań witryn zarządzanych. nexus_is_valid_request() , nexus_is_granted() i cerber_nexus_seal działają bez zmian. Przekazane zgłoszenie nadal weryfikuje wartość nonce wtyczki przed uruchomieniem jakiegokolwiek przetwarzania ustawień. Dodaliśmy również kontrolę punktu wejścia. Przesłany ekran ustawień, który nie prowadzi do znanego ekranu, jest teraz zamykany z błędem WP_Error przed rozpoczęciem przetwarzania, a komunikat o błędzie zawiera przesłaną wartość diagnostyczną.
Twarda zmiana i jedna rzecz, o której należy wiedzieć po aktualizacji
Było to celowe, twarde przełączenie. Nie zachowaliśmy przejściowej ścieżki dwuformatowej i nie zachowaliśmy przetwarzania options.php . Z tą decyzją wiąże się jeden skrajny przypadek, o którym warto powiedzieć wprost. Jeśli formularz ustawień został wyrenderowany przez poprzednią wersję WP Cerber i prześlesz go po aktualizacji, może się nie zapisać za pierwszym razem. W takim przypadku otwórz ponownie stronę ustawień i prześlij go ponownie. Świeżo wyrenderowany formularz zawiera nowy wewnętrzny kontrakt i zapisuje się normalnie. To samo dotyczy zdalnych formularzy ustawień w witrynach zarządzanych.
Migracja wiązała się z porządkowaniem nazw. Przeciążona group terminów została przemianowana na settings_screen_id wszędzie tam, gdzie identyfikowała ekran ustawień, w tym dla stałej, ukrytej wartości pola formularza, sygnatur funkcji potoku i kluczy konfiguracji. Zdalny kod błędu został przemianowany z unknown_settings_group na unknown_settings_screen . Jeden szczegół dotyczący zgodności ma znaczenie dla twórców dodatków. Ładunek zdarzenia update_settings nadal zawiera stary klucz group jako alias settings_screen_id , ponieważ cerber_add_handler() jest publiczna i zewnętrzne procedury obsługi mogą go odczytać.
Podstawy UI Factory
Na ekranach administracyjnych WP Cerber renderuje HTML za pomocą wewnętrznej fabryki interfejsu użytkownika (UI Factory), a nie znaczników inline. W tej wersji dodano dwa strukturalne bloki konstrukcyjne. crb_ui_fragment() generuje mieszaną, uporządkowaną kolekcję elementów potomnych bez znacznika opakowującego. crb_ui_element_set() robi to samo dla kolekcji jednorodnej z wymuszonym typem potomnym. Zmieniliśmy nazwę płynnego CRB_UI_Fragment_Builder na CRB_UI_Content_Builder , aby uniknąć pomyłki z nowym węzłem fragmentu, i zachowaliśmy alias klasy, aby istniejący kod nadal działał.
W rezultacie kilka stron wywołań stało się prostszych. crb_ui_message_box() akceptuje teraz zwykłe ciągi znaków i liczby, a następnie umieszcza je w elementach akapitu ze znakami ucieczki, dzięki czemu osoby wywołujące nie muszą już tworzyć tych akapitów ręcznie. Zduplikowany komunikat o błędzie pobierania na pulpicie nawigacyjnym i ekran dziennika ruchu zostały przeniesione do jednego, współdzielonego narzędzia pomocniczego. Panel diagnostyki środowiska został przebudowany na nowych węzłach. W każdym przypadku renderowany kod HTML pozostaje niezmieniony. Stanowi to podstawę dla niezależnego od renderera interfejsu użytkownika administratora i obecnie jest niewidoczny na stronie.
Po uaktualnieniu
Krótka lista kontrolna dla administratorów:
- Jeśli formularz ustawień nie zostanie zapisany przy pierwszym przesłaniu zaraz po aktualizacji, otwórz ponownie stronę ustawień i jeszcze raz je zapisz.
- Jeśli WP Cerber kiedykolwiek powróci do ustawień domyślnych lub przywróci je z kopii zapasowej, powiadomi Cię o tym za pośrednictwem błędu administratora. Sprawdź ustawienia i zapisz je, aby usunąć komunikat.
- Jeśli Twój host obsługuje PHP bez sterownika mysqlnd, poszukaj komunikatu w widżecie „Gotowość systemu”. Wtyczka nadal działa, a powiadomienie wyjaśnia zalecaną zmianę.