Under the Hood of WP Cerber 9.9
English version: Under the Hood of WP Cerber 9.9
Questa release riguarda principalmente le parti di WP Cerber che non si notano finché non si verifica un problema. Ci siamo dedicati a rafforzare la sicurezza del salvataggio e del recupero delle impostazioni, la lettura del codice JavaScript offuscato da parte di Traffic Inspector e il comportamento del plugin su infrastrutture di hosting meno recenti. Abbiamo inoltre completato una lunga migrazione dall'API delle impostazioni di WordPress. Ecco cosa è cambiato "dietro le quinte" e cosa significa per i siti che gestite.
Impostazioni che sopravvivono a un database danneggiato
WP Cerber memorizza la sua configurazione in un'unica opzione, CERBER_CONFIG . Fino ad ora, se questo valore si corrompeva e non poteva più essere deserializzato, il plugin passava il risultato errato direttamente a array_merge() . Su PHP 8, questo generava un errore irreversibile TypeError al caricamento del plugin. Poiché l'errore si verificava durante il caricamento, l'intero sito risultava non disponibile, e non solo le schermate di amministrazione del plugin.
crb_get_settings() ora convalida il valore restituito crb_unserialize() prima di utilizzarlo. Quando i dati memorizzati non possono essere convertiti in un array, il plugin ripristina le impostazioni predefinite anziché bloccarsi. Registra l'errore come problema critico persistente tramite CRB_Issues::add() con il codice corrupted_settings . Tale problema si risolve automaticamente al successivo salvataggio delle impostazioni, operazione che viene eseguita all'interno cerber_settings_update() .
Abbiamo avuto cura di distinguere due casi che sembrano simili ma non lo sono. Un valore memorizzato vuoto o comunque errato significa semplicemente che le impostazioni non esistono ancora. Tale valore non viene mai deserializzato e il plugin ripristina silenziosamente le impostazioni predefinite senza generare alcun allarme. Segnalarlo sarebbe un falso allarme. Il problema corrupted_settings ora si verifica solo quando un valore memorizzato non vuoto non riesce a essere deserializzato in un array, che è esattamente la condizione alla base del crash originale in produzione.
Due rami vicini nella stessa funzione hanno ricevuto la stessa disciplina. La fusione di compatibilità del componente aggiuntivo Cloudflare ora richiede un array prima di poter essere fusa. Il ramo CERBER_WP_OPTIONS restituisce sempre un array quando non viene richiesta alcuna impostazione specifica.
Un sistema di backup autoriparante sopra la protezione
Il ripristino delle impostazioni predefinite consente al sito di continuare a funzionare, ma comporta anche la perdita della configurazione dell'amministratore. Per questo motivo, abbiamo aggiunto un livello di ripristino aggiuntivo. Una nuova classe, CRB_Settings_Backup , conserva una copia valida dell'ultima versione nota di CERBER_CONFIG nell'archivio chiave-valore del plugin.
Il backup viene memorizzato come JSON grezzo insieme all'ID dell'utente che lo ha creato, a una data e ora e al contesto che lo ha generato. Viene aggiornato dopo un aggiornamento delle impostazioni riuscito, dopo un'importazione delle impostazioni, dopo un aggiornamento di un plugin e dall'attività di manutenzione giornaliera. Solo una configurazione valida e un codice di contesto noto possono sostituire un backup esistente.
Quando il rilevatore di corruzione trova un CERBER_CONFIG illeggibile, il ripristino viene eseguito automaticamente. In caso di ripristino riuscito, il plugin registra un avviso che può essere chiuso, il quale spiega cosa è successo e chiede di rivedere e salvare le impostazioni. Tale avviso scompare una volta salvate le impostazioni. Se non esiste un backup utilizzabile o se il valore ripristinato non può essere scritto, il plugin mantiene il suo comportamento predefinito di ultima istanza e ripristina le impostazioni predefinite. In tal caso, registra un problema critico anziché l'avviso.

WP Cerber settings recovery flow. Missing settings quietly fall back to defaults, while corrupted settings trigger automatic recovery from the last-known-valid backup.
Per un ripristino corretto è fondamentale rispettare la cache di WordPress. La funzione recover() elimina l'opzione CERBER_CONFIG corrotta prima di scrivere il valore ripristinato. Senza questo passaggio, una cache delle opzioni obsoleta potrebbe far sì che update_site_option() consideri il valore come invariato e salti la scrittura nel database. Il backup stesso viene letto e scritto senza la cache degli oggetti, quindi solo il record cerber_sets persistente è considerato affidabile. L'insieme dei codici di contesto accettati è chiuso. Il writer, sync() , e il validatore del payload accettano solo i quattro codici dichiarati, in modo che ogni payload memorizzato possa superare la stessa convalida che il ripristino eseguirà in seguito.
Niente più errori irreversibili sugli host senza mysqlnd
WP Cerber legge i dati dal database tramite mysqli. Un percorso di recupero, CRB_Database::fetch_result_set() , utilizza mysqli_result::fetch_all() per recuperare un intero set di risultati in una singola chiamata. Questo metodo esiste solo quando l'estensione mysqli di PHP è compilata con il driver mysqlnd. Sui sistemi host in cui mysqli è compilato con la vecchia libreria libmysqlclient, il metodo non è presente e la sua chiamata genera un errore irreversibile.
La correzione segue uno schema già utilizzato altrove in cerber-common.php . Prima di intraprendere il percorso più veloce, il codice ora verifica la function_exists('mysqli_fetch_all') . Quando tale funzione non è disponibile, legge il set di risultati riga per riga con mysqli_result::fetch_array() , sia per la struttura MYSQLI_ASSOC che per quella MYSQLI_NUM . L'ordine delle righe, la struttura del risultato, la pulizia e il contratto di ritorno Revalt esistente vengono tutti preservati. Il fallback restituisce gli stessi dati del percorso più veloce.
Abbiamo inoltre fornito agli amministratori un modo per sapere quando si attiva il fallback. Un nuovo rilevatore in CRB_Issue_Monitor segnala un problema, db_driver_no_mysqlnd , quando l'estensione mysqli è caricata ma manca mysqli_fetch_all() . L'avviso viene visualizzato nel widget di verifica della compatibilità del sistema. Ha scopo informativo: conferma che il plugin continua a funzionare anche in caso di fallback e consiglia di abilitare mysqlnd per una migliore compatibilità e prestazioni.
Rilevamento più preciso del codice JavaScript offuscato
CRB_JS_Detector analizza i campi delle richieste come parte di Traffic Inspector, che è abilitato per impostazione predefinita. Cerca codice JavaScript offuscato per nascondere elementi come eval , script e XMLHttpRequest . L'obiettivo di progettazione è un rilevamento ad alta affidabilità con un basso tasso di falsi positivi. Il rilevatore decodifica solo gli input che sono inequivocabilmente stringhe codificate e lascia inalterato tutto il resto. Diverse modifiche in questa versione hanno sia risolto una vulnerabilità di elusione reale, sia ampliato i campi che il rilevatore è in grado di leggere.
Una regressione che ha permesso il passaggio di stringhe completamente codificate in esadecimale
La release includeva la correzione di un bug. L'euristica di escape esadecimale normalizzava ogni stringa corrispondente con trim( $m, "\\'\"" ) . La trim() di PHP tratta il suo secondo argomento come un insieme di caratteri da rimuovere, non come un prefisso letterale. La barra rovesciata in quell'insieme rimuoveva la barra rovesciata iniziale del primo escape \xNN insieme all'apostrofo di apertura. Ciò che rimaneva aveva una lunghezza dispari di 2N + 1 Un controllo di lunghezza dispari decideva quindi che la stringa non era codificata correttamente in esadecimale e saltava completamente la decodifica. Il risultato pratico era che una stringa costruita esclusivamente con escape \xNN sfuggiva al controllo e i primitivi con escape esadecimale come eval , script e XMLHttpRequest non venivano rilevati nel percorso predefinito del campo di richiesta. La correzione ripristina la corretta decodifica delle stringhe completamente codificate in esadecimale.
Copertura più ampia dei codici di escape e dei caratteri
Abbiamo quindi ampliato le capacità di comprensione del rilevatore. L'euristica di escape in precedenza riconosceva solo \xNN . Ora gestisce anche gli escape \uNNNN e \u{...} , comprese le stringhe che mescolano questi formati, mantenendo la regola che vengano ispezionate solo le stringhe completamente elaborate con escape. La decodifica è stata spostata in una funzione di supporto dedicata, cerber_decode_js_escapes() , che decodifica i punti di codice ASCII e preserva tutto il resto. Se si verifica un errore PCRE interno, questa funzione di supporto restituisce l'input originale anziché una stringa vuota, in modo che un errore di decodifica non possa cancellare il valore controllato.
Anche l'euristica del codice carattere è cambiata. In precedenza esaminava l'output decodificato solo per URL esterni e indirizzi IP. Ora cerca anche primitive di esecuzione e DOM. Decodifica i numeri solo da una costruzione esplicita fromCharCode(...) anziché da qualsiasi array numerico che incontra. Entrambe le euristiche ora condividono un modello primitivo consapevole del token con i limiti dell'identificatore. Questa modifica impedisce che parole comuni come description e evaluation corrispondano alle sottostringhe eval o script al loro interno.
Letterali interi avvolti
fromCharCode è definita per applicare ToUint16 a ciascun argomento. Ciò significa che un codice ASCII più qualsiasi multiplo di 65536 produce lo stesso carattere. Una versione precedente dell'euristica accettava solo valori letterali fino a sei cifre, quindi un valore a sette cifre con avvolgimento passava inosservato. Ad esempio, 1048677 si decodifica nell'unità di codice 101, la lettera " e . Il rilevatore ora accetta valori letterali decimali senza segno ed esadecimali fino a Number.MAX_SAFE_INTEGER e riduce ciascuno di essi alla sua unità di codice ToUint16 indipendentemente dalla dimensione intera di PHP. Rifiuta i decimali con zeri iniziali, perché sono ambigui con il vecchio formato ottale. Limita il lavoro per cifra in modo che un valore letterale molto lungo in una richiesta pubblica non possa generare calcoli illimitati.
Un successivo aggiornamento ha colmato una lacuna correlata. Restituire una stringa vuota sul primo argomento non supportato ha comportato l'interruzione dell'intera chiamata decodificata. Un utente malintenzionato potrebbe aggiungere un singolo token fuori intervallo a un payload altrimenti rilevabile e impedire il rilevamento. Un esempio di tale token è 9007199254741024 , che JavaScript mappa a uno spazio tramite ToUint16. Ora un token strutturalmente valido ma non supportato viene sostituito con un underscore come sentinella, e il resto della chiamata viene comunque decodificato. L'underscore è un carattere alfanumerico, quindi impedisce anche la formazione di un confine di identificatore fittizio accanto a una parola chiave.
Argomenti separati da commenti
L'ultimo tipo di elusione utilizzava i commenti JavaScript. L'euristica compattava l'input rimuovendo gli spazi bianchi, lasciando i commenti al loro posto. Un commento inserito tra argomenti numerici interrompeva la corrispondenza tra numeri e lista, quindi una chiamata come String.fromCharCode(101,/*x*/118,97,108,40,49,41,59) non veniva rilevata. Il rilevatore ora rimuove i commenti a blocchi e di riga dalla lista di argomenti fromCharCode prima di convalidarla e decodificarla, preservando al contempo il contenuto delle stringhe tra virgolette. L'acquisizione viene inoltre eseguita in modalità dotall, quindi una lista di argomenti che si estende su più righe viene letta come una singola chiamata.
Dismissione dell'API delle impostazioni di WordPress.
Le pagine delle impostazioni di WP Cerber erano basate sull'API delle impostazioni di WordPress. I moduli venivano inviati a /wp-admin/options.php e il plugin si affidava alle register_setting() , add_settings_section() , add_settings_field() , settings_fields() e do_settings_sections() per registrarli e visualizzarli. Questo sistema funzionava, ma vincolava l'interfaccia utente di amministrazione del plugin a un sottosistema procedurale di WordPress e alle sue convenzioni. Questa versione completa la transizione a un motore di moduli gestito dal plugin, e l'abbiamo realizzata gradualmente anziché con una singola modifica.
Innanzitutto abbiamo definito un confine. Ogni chiamata diretta all'API delle impostazioni è stata spostata in una classe statica, CRB_Legacy_Settings_Manager . Un'analisi del codice sorgente ha rivelato che solo cinque delle dieci funzioni dell'API delle impostazioni venivano utilizzate, distribuite in sei punti di chiamata e due file. Le altre cinque non venivano mai chiamate, quindi la classe di confine non contiene metodi per esse. Questo passaggio ha preservato il comportamento originale. Nomi delle opzioni, gruppi di opzioni, ID di sezione e di campo, callback, tempi di hook e output sono rimasti invariati.
Abbiamo quindi creato la sostituzione. Una nuova classe, CRB_Settings_Renderer , renderizza sezioni e righe di campi direttamente dalla configurazione dichiarativa restituita da cerber_settings_config() . I moduli delle impostazioni ora inviano i dati alla pagina di amministrazione del plugin anziché a options.php . Gli invii vengono elaborati dalla pipeline esistente in admin_init e successivamente seguiti da un reindirizzamento GET tramite POST alla pagina delle impostazioni. Il markup renderizzato è identico a livello di byte a quello prodotto in precedenza da do_settings_sections() , comprese le intestazioni, i blocchi di sezione e le righe della tabella dei moduli.
La verifica del nonce è cambiata con questo aggiornamento. Invece di utilizzare check_admin_referer() rispetto a un gruppo di opzioni dell'API delle impostazioni, il plugin ora verifica il proprio campo cerber_nonce . Questo campo era già presente in ogni modulo delle impostazioni, inclusi quelli visualizzati dalle versioni precedenti. Un nonce non valido o scaduto non blocca più la richiesta con wp_die() , ma mette in coda una notifica per l'amministratore e ti riporta al modulo.
L'eliminazione del options.php ha comportato anche la rimozione di un comportamento obsoleto non immediatamente evidente. Il vecchio metodo prevedeva la scrittura di una copia grezza di ciascun gruppo di moduli in un'opzione specifica per ogni gruppo, denominata cerber-{group} . Queste copie venivano lette solo dalla migrazione precedente alla versione 9.3.4 e dalla pulizia post-disinstallazione, mai in fase di esecuzione. Ora non vengono più scritte.
Cerber.Hub e siti gestiti
Due contesti hanno mantenuto il vecchio percorso per un po' di tempo, perché entrambi dipendevano dal formato di comunicazione dell'API delle impostazioni. Uno era il rendering remoto di Cerber.Hub, dove il protocollo tra un sito principale e i suoi siti gestiti veicolava i campi option_page e _wpnonce . L'altro era la schermata di modifica del sito gestito, che salvava tramite un filtro pre_update_option attivato da options.php . Abbiamo migrato ciascuno di essi a turno.
Il modulo di modifica del sito gestito ora viene renderizzato tramite CRB_Settings_Renderer e salvato tramite una nuova funzione, nexus_save_client_data_form() . Questa funzione riproduce esattamente la gestione dei dati del vecchio callback, inclusa la risoluzione dei gruppi, la sanificazione dei dettagli del proprietario strip_tags e la pulizia dei gruppi non utilizzati. Mantiene lo stesso confine di sicurezza con una protezione esplicita nexus_is_main() e is_super_admin() . Anche il client Cerber.Hub ha quindi abbandonato l'emulazione dell'API Impostazioni. Successivamente, sono stati eliminati CRB_Legacy_Settings_Manager e diversi helper ormai obsoleti.
I moduli delle impostazioni remote non emettono più i campi nascosti dell'API delle impostazioni come option_page , action=update , _wpnonce o _wp_http_referer . Ora contengono gli stessi campi interni dei moduli locali, oltre al sigillo Nexus nel contesto remoto. L'autenticazione del trasporto Nexus rimane invariata e rappresenta il limite esterno per le richieste dei siti gestiti. nexus_is_valid_request() , nexus_is_granted() e il round trip cerber_nexus_seal funzionano come prima. Un invio inoltrato verifica ancora il nonce del plugin prima che venga eseguita qualsiasi elaborazione delle impostazioni. Abbiamo anche aggiunto un controllo del punto di ingresso. Una schermata delle impostazioni inviata che non si risolve in una schermata nota ora viene chiusa con un WP_Error prima che inizi l'elaborazione e il messaggio di errore include il valore inviato per la diagnostica.
Un passaggio brusco e una cosa da sapere dopo l'aggiornamento
Si è trattato di un passaggio brusco e deliberato. Non abbiamo mantenuto un percorso di transizione a doppio formato e non è rimasta alcuna elaborazione da parte options.php . Questa decisione comporta un caso limite, che vale la pena di specificare chiaramente. Se un modulo di impostazioni è stato generato dalla versione precedente di WP Cerber e lo si invia dopo l'aggiornamento, potrebbe non essere salvato correttamente. In tal caso, riaprire la pagina delle impostazioni e inviarla nuovamente. Il modulo appena generato conterrà il nuovo contratto interno e verrà salvato normalmente. Lo stesso vale per i moduli di impostazioni remoti sui siti gestiti.
La migrazione è stata accompagnata da una pulizia della nomenclatura. Il termine sovraccaricato " group è stato rinominato in settings_screen_id ovunque identificasse una schermata delle impostazioni, inclusi la costante, il valore del campo modulo nascosto, le firme delle funzioni della pipeline e le chiavi di configurazione. Il codice di errore remoto è stato rinominato da unknown_settings_group a unknown_settings_screen . Un dettaglio di compatibilità è importante per gli autori di componenti aggiuntivi. Il payload dell'evento update_settings " contiene ancora la vecchia chiave group come alias di settings_screen_id , perché cerber_add_handler() è pubblico e i gestori esterni possono leggerlo.
Fondamenta della UI Factory
Nelle schermate di amministrazione, WP Cerber renderizza l'HTML tramite una UI Factory interna anziché tramite markup inline. Questa versione ha aggiunto due blocchi strutturali. crb_ui_fragment() produce una collezione mista e ordinata di elementi figlio senza tag di wrapping. crb_ui_element_set() fa lo stesso per una collezione omogenea con un tipo di elemento figlio imposto. Abbiamo rinominato la classe fluida CRB_UI_Fragment_Builder in CRB_UI_Content_Builder per evitare confusione con il nuovo nodo frammento e abbiamo mantenuto un alias di classe in modo che il codice esistente continui a funzionare.
Di conseguenza, alcuni punti di chiamata sono diventati più semplici. crb_ui_message_box() ora accetta stringhe e numeri semplici e li racchiude in elementi di paragrafo con caratteri di escape, in modo che gli utenti non debbano più creare manualmente tali paragrafi. L'avviso di errore di recupero duplicato nella dashboard e la schermata del registro del traffico sono stati spostati in un unico helper condiviso. Il pannello di diagnostica dell'ambiente è stato ricostruito sui nuovi nodi. In ogni caso, l'HTML renderizzato rimane invariato. Questo getta le basi per un'interfaccia utente di amministrazione indipendente dal renderer, ed è attualmente invisibile sulla pagina.
Dopo l'aggiornamento
Una breve lista di controllo per gli amministratori:
- Se un modulo di impostazioni non viene salvato al primo invio subito dopo l'aggiornamento, riapri la pagina delle impostazioni e salvalo di nuovo.
- Se WP Cerber dovesse ripristinare le impostazioni predefinite o recuperarle da un backup, ti avviserà tramite un messaggio nell'area amministrativa. Controlla le impostazioni e salvale per far scomparire l'avviso.
- Se il tuo host esegue PHP senza il driver mysqlnd, cerca l'avviso nel widget "Condizioni di sistema". Il plugin continua a funzionare e l'avviso spiega la modifica consigliata.