Under The Hood 9.8.3
English version: Under The Hood 9.8.3
Questa release rappresenta un ciclo di manutenzione e rafforzamento della sicurezza, piuttosto che il lancio di una nuova funzionalità. La maggior parte del lavoro si è concentrata su aspetti che raramente vengono presi in considerazione dai proprietari dei siti: il livello dati che legge i log di sicurezza, il codice che compone le email di notifica e i percorsi di esportazione che forniscono un file CSV con i record di attività. Nessuno di questi interventi modifica l'aspetto del plugin, ma tutti ne migliorano l'affidabilità e la sicurezza in situazioni di traffico intenso, log di grandi dimensioni o attacchi informatici che sfruttano vulnerabilità non ancora risolte.
Di seguito viene riportata la descrizione a livello di implementazione per amministratori e sviluppatori che desiderano comprendere cosa è effettivamente cambiato.
Chiusura di un vettore di iniezione del destinatario di posta elettronica
WP Cerber invia due tipi di email transazionali che includono il nome di una persona: il messaggio con il PIN per l'autenticazione a due fattori e la notifica di avviso di attività. Entrambe costruiscono il destinatario nel formato standard RFC 5322 Name <email> ed entrambe prelevano il nome direttamente dai campi del profilo WordPress ( user_firstname , user_lastname e display_name ) senza effettuare l'escape.
Il problema risiede nel modo in cui wp_mail() gestisce un destinatario di tipo stringa. Divide la stringa in base a una virgola letterale e non rispetta i limiti delle stringhe racchiuse tra virgolette. Un utente che inserisse i caratteri corretti nel proprio nome profilo potrebbe quindi inserire un indirizzo aggiuntivo nell'elenco dei destinatari dell'email di autenticazione a due fattori (2FA) generata da CRB_2FA::send_user_pin() e dell'email di avviso generata tramite il ramo user_list di cerber_get_email() . Su un sito in cui chiunque può registrarsi e impostare un nome visualizzato, questo rappresenta un modo concreto per reindirizzare silenziosamente una copia di un messaggio di sicurezza.
Abbiamo aggiunto una funzione di sanificazione dedicata, crb_sanitize_mail_display_name() , nel cerber-common.php e l'abbiamo applicata a entrambi i siti di chiamata. Questa funzione rimuove esattamente i caratteri rilevanti per questo tipo di injection: la virgola, le virgolette, la barra rovesciata, le parentesi angolari e i caratteri di controllo. Il nome visualizzato viene comunque visualizzato normalmente per gli utenti legittimi e l'elenco dei destinatari non può più essere manipolato tramite un campo del profilo appositamente creato.
Rimozione di un vettore XSS memorizzato nello scanner di proprietà
Lo scanner antimalware segnala quando cambia la proprietà di un plugin installato sul repository di WordPress.org. Per fare ciò, utilizza i metadati di proprietà forniti da tale repository e, fino ad ora, integrava parte di questi metadati nell'avviso di amministrazione come codice HTML grezzo, inclusi tag di ancoraggio creati manualmente per i link al profilo del proprietario.
Quel repository è una fonte attendibile e la probabilità di metadati dannosi è bassa, ma i dati esterni non dovrebbero mai raggiungere l'interfaccia di amministrazione come markup non codificato. Considerarlo attendibile è proprio il presupposto su cui preferiamo non fare affidamento.
Per chiudere correttamente il vettore anziché correggerlo localmente, abbiamo introdotto un nuovo elemento UI Factory, formatted_text , costruito tramite la funzione di supporto crb_ui_formatted_text() . Questo elemento renderizza un modello di testo semplice che contiene segnaposto numerati %N$s . I frammenti letterali del modello e gli argomenti scalari vengono sottoposti a escape HTML, mentre qualsiasi argomento che sia esso stesso un elemento UI viene renderizzato tramite il renderer attivo. Ogni valore dinamico viene quindi sottoposto a escape nel contesto in cui viene effettivamente emesso, che è l'unico modo affidabile per prevenire errori di contesto di output.
Il messaggio di proprietà in crb_check_ownership() ora utilizza questo elemento e i link del profilo del proprietario vengono creati con crb_ui_link() invece di stringhe di ancoraggio concatenate, quindi gli URL del profilo e i nomi visualizzati vengono rispettivamente sottoposti a escape come URL e come testo. La stringa di traduzione rimane invariata, quindi le localizzazioni continuano a funzionare. Il rischio XSS per l'amministratore memorizzato è stato eliminato.
Correzione del filtro "Qualsiasi errore software" in Traffic Inspector
Nel registro di Traffic Inspector, il modulo di ricerca avanzata consente di combinare diverse condizioni. Una di queste è la casella di controllo "Qualsiasi errore software". Combinando questa casella di controllo con altri filtri, le richieste che presentavano un errore PHP registrato potevano apparire nei risultati anche se non corrispondevano alle altre condizioni specificate.
La causa principale risiedeva nella precedenza degli operatori nel modo in cui veniva assemblata la clausola WHERE. Il codice precedente concatenava i frammenti del filtro come stringhe e la condizione di errore raggruppata non era racchiusa tra parentesi, consentendo così a un OR al suo interno di sfuggire alla logica AND circostante. Con lo spostamento della query sul query builder, la condizione di errore raggruppata è ora correttamente racchiusa tra parentesi e il filtro combinato si comporta come previsto dalla sua forma. Quando si restringe una ricerca, i risultati ora rispettano tutte le condizioni impostate.
Corrispondenza dei caratteri jolly di ricerca letteralmente
In precedenza, la ricerca avanzata dei registri di traffico consentiva a un % o _ digitati nel termine di ricerca di agire come un metacarattere LIKE , poiché il termine veniva inserito nel modello senza essere sottoposto a escape. Questa è una funzionalità accidentale nella migliore delle ipotesi e un modello di caricamento non necessario nella peggiore.
Ogni termine LIKE ora passa attraverso CRB_Database::escape_like() prima che vengano aggiunti i caratteri jolly circostanti, quindi % e _ vengono interpretati come i caratteri letterali digitati dall'utente. Le ricerche si comportano in modo prevedibile e la query non può essere forzata a eseguire una scansione più ampia di quella prevista.
Correzione della gestione dei limiti di memoria per le esportazioni
L'esportazione di un registro di attività o traffico di grandi dimensioni richiede molta memoria, pertanto WP Cerber aumenta la memoria disponibile prima dell'operazione. In alcuni ambienti, un valore numerico per il limite di memoria, come 512 veniva interpretato come byte anziché come megabyte. In tal caso, il plugin non riusciva ad aumentare il limite come previsto e l'esportazione poteva interrompersi prima del previsto.
Il valore viene ora interpretato con l'unità corretta, pertanto l'aumento di memoria viene applicato come previsto e le esportazioni di grandi dimensioni vengono completate negli ambienti che in precedenza erano interessati.
Ricostruzione delle esportazioni dei log in base allo streaming
Le esportazioni di Attività e Traffico venivano utilizzate per leggere le righe corrispondenti in blocchi, rieseguendo la stessa SELECT con un OFFSET crescente per ogni blocco. Su un log di grandi dimensioni, questo processo si riduceva a una scansione con offset profondo, in cui ogni blocco costava più del precedente e manteneva in memoria un risultato crescente.
Entrambe le esportazioni ora leggono le righe in un singolo passaggio non bufferizzato attraverso CRB_Database::query_stream() , incapsulato in un generatore che restituisce una riga alla volta. La memoria rimane invariata indipendentemente dal numero di record corrispondenti e il database esegue l'operazione una sola volta anziché una volta per blocco. Poiché uno stream non bufferizzato blocca la connessione durante il consumo, il totale delle righe e l'intervallo di date vengono risolti in anticipo con query COUNT e MIN / MAX bufferizzate separate, costruite a partire dagli stessi filtri, prima che lo stream venga aperto.
Sono state aggiunte due intestazioni di risposta alla funzione di supporto per il download di file condivisi, crb_file_headers() . X-Accel-Buffering: no indica a Nginx, quando funge da front-end per PHP-FPM, di inoltrare ogni blocco immediatamente invece di memorizzare in un buffer l'intero export prima di inviarlo, migliorando così il time-to-first-byte ed evitando che il proxy mantenga un file CSV di grandi dimensioni in memoria. L'intestazione è specifica di Nginx e viene ignorata senza causare problemi da Apache con mod_php e da altri proxy. Cache-Control: no-store impedisce al browser o a qualsiasi proxy intermedio di memorizzare nella cache un export di log di sicurezza sensibile.
Abbiamo inoltre reso deterministica la pulizia dello stream. L'esportazione consuma il generatore all'interno di un blocco try / finally e rilascia l'unico handle rimanente nel finally , in modo che il risultato non bufferizzato venga liberato e la connessione sbloccata ad ogni percorso di uscita: completamento normale, interruzione anticipata o eccezione generata durante la scrittura di una riga. In precedenza, un'interruzione anticipata o un errore prima del consumo completo potevano lasciare il risultato aperto e la connessione bloccata, causando il fallimento della query successiva in quella richiesta.
Segnalazione dell'intervallo di date esportate
In precedenza, l'intestazione di esportazione CSV riportava solo i filtri attivi. Ora, sia l'esportazione di Attività che quella di Traffico aggiungono due righe all'intestazione, che mostrano i timestamp dei record più vecchi e più recenti coperti dai dati esportati. Poiché l'intestazione viene scritta prima della prima riga, l'intervallo deriva da una query MIN / MAX correlata, creata a partire dalla stessa query filtrata utilizzata per l'esportazione, e viene omessa quando non vengono trovate righe corrispondenti. Quando si archivia un'esportazione, il file ora registra l'intervallo di tempo esatto che rappresenta.
Rendere visibili, anziché silenziosi, gli errori di esportazione.
Il vecchio metodo di esportazione poteva fallire silenziosamente. Se il database non era disponibile o non era possibile aprire il flusso di righe, il codice precedente tendeva a produrre un file CSV vuoto senza alcuna spiegazione, il che rappresenta il peggior risultato possibile per chiunque tenti di estrarre record durante un incidente.
I percorsi di lettura ed esportazione per entrambi i log sono stati rielaborati per restituire un risultato Revalt che contiene il payload dei dati o un errore strutturato, con codici distinti come activity_export_query_build_failed , activity_export_db_unavailable e activity_export_stream_failed . Un errore di livello inferiore viene concatenato al risultato in modo che la causa principale originale venga preservata anziché scartata. Gli errori di configurazione ora terminano l'esportazione con wp_die() prima che venga inviato un singolo byte CSV, invece di trasmettere in streaming un file vuoto.
Quando un'esportazione fallisce, un amministratore in possesso della capacità manage_options visualizza la causa principale a catena, ad esempio l'errore del database sottostante, aggiunta al messaggio. Gli utenti senza tale capacità non la visualizzano, quindi i dettagli utili per l'azione raggiungono le persone che possono intervenire, mentre le specifiche interne del database rimangono riservate. Il messaggio viene elaborato tramite la crb_escape_html() .
Consolidamento dei registri di attività e traffico in classi di dominio
Gran parte di questo ciclo è stato di natura strutturale. Il registro delle attività e il registro del traffico contenevano stringhe SQL e gestione dei risultati sparse nel codice del dashboard e dell'esportazione. Abbiamo spostato questa logica nelle classi CRB_Activity e CRB_Traffic_Log , in modo che la creazione delle query e il recupero delle righe siano ora gestiti da metodi chiari come fetch() e stream_log() anziché transitare attraverso il codice di presentazione come SQL grezzo.
Entrambe le classi ora costruiscono le loro query con il query builder DB Warp ottenuto tramite warp_get_db() invece di concatenare manualmente i frammenti WHERE, JOIN e LIMIT. Questo è importante non solo per una questione di ordine. Instradare ogni valore di filtro fornito dall'utente attraverso un singolo livello di escape elimina la precedente combinazione di escape manuale, $wpdb->prepare() e concatenazione manuale tra virgolette, che è esattamente il tipo di incoerenza che nasconde bug di injection. Quando un livello del database degradato non è in grado di costruire una query, il codice ora fallisce in modo sicuro con una condizione di mancata corrispondenza invece di eseguire una query non filtrata.
Questi refactoring sono interni e non modificano ciò che viene visualizzato a schermo, ma costituiscono la base che ha reso le correzioni di sicurezza e affidabilità di cui sopra piccole, locali e verificabili.
Sono stati rilevati due bug latenti durante l'estrazione del codice di allerta.
Spostando la gestione degli avvisi amministrativi da CRB_Activity::log() a una classe dedicata, CRB_Activity_Alerts sono emersi due bug preesistenti causati da un array posizionale i cui indici numerici si erano disallineati.
Il primo problema riguardava i link del pannello di controllo all'interno delle email di avviso. L'associazione tra chiavi sparse e valori densi causava uno spostamento dei valori, per cui un parametro di collegamento come filter_ip poteva ricevere l'inizio di un intervallo IP anziché l'indirizzo previsto. La mappatura dei valori su chiavi denominate ha corretto l'allineamento e ora i link nelle email di avviso puntano alla posizione corretta.
Il secondo problema riguardava la corrispondenza degli utenti nella stringa di ricerca. Il codice chiamava wp_get_current_user() , che restituiva un oggetto WP_User anche per l'utente con ID 0, quindi la corrispondenza utilizzava l'identità sbagliata. Ora il codice cerca l'utente dell'evento stesso con crb_get_userdata() e si assicura che l'utente non sia presente. Gli avvisi che corrispondono a un utente ora corrispondono a quello corretto.
Un cruscotto più silenzioso: precedenza dell'operatore nel controllo delle modifiche
CRB_Activity::is_modified_since() confrontava un timestamp usando $stamp < $status['data_modified'] ?? PHP_INT_MAX . La precedenza dell'operatore lo lega come (... < ...) ?? PHP_INT_MAX , il che rendeva la parte di coalescenza null codice morto e generava un avviso di chiave non definita ogni volta che data_modified era assente. La coalescenza è ora tra parentesi, quindi un timestamp di modifica mancante viene trattato come "modificato", corrispondendo al metodo gemello is_modified() , e l'avviso spurio è scomparso.
Centralizzazione delle definizioni dello schema del database
L'ultimo elemento strutturale riguarda la manutenzione dello schema. Il codice di installazione e aggiornamento utilizzava istruzioni SQL CREATE TABLE inline per le tabelle di log, il che significava che la stessa tabella poteva essere descritta in più punti. Ora queste dichiarazioni provengono da un'unica fonte, CRB_Schema_Definitions , quindi l'installazione e l'aggiornamento si basano su un'unica definizione canonica e CRB_Schema_Manager può rilevare le modifiche dello schema nelle installazioni esistenti in modo più coerente. Anche le istruzioni SQL DROP INDEX raw sono state sostituite con CRB_Schema_Manager::drop_index_if_exists() .
Abbiamo inoltre reso più tollerante la decodifica dei dati memorizzati nei campi di richiesta. I valori legacy nullable, i valori vuoti, i JSON non validi e i payload serializzati non supportati vengono ora gestiti tutti in modo sicuro, risolvendoli in un array vuoto anziché lasciare che un record non valido interrompa la lettura.
Perché questa pubblicazione è importante
Nella versione 9.8.3 non ci sono nuovi pulsanti. Ciò che troviamo, invece, è un livello dati che gestisce gli errori in modo sicuro anziché silenzioso, un'email di notifica che non può essere indirizzata tramite un nome profilo creato ad hoc, un avviso per l'amministratore che gestisce correttamente i dati esterni, esportazioni di log che vengono eseguite in memoria e segnalano eventuali problemi, e filtri di ricerca che fanno esattamente ciò che è specificato nel modulo. Sono questi i tipi di modifiche che mantengono un plugin di sicurezza affidabile nel lungo termine, e preferiamo svolgere questo lavoro in modo trasparente piuttosto che fingere che il codice precedente fosse già perfetto.