Fai attenzione: funzioni PHP e WordPress che possono rendere insicuro il tuo sito
Pubblicato: 2022-03-10La sicurezza di un sito Web WordPress (o qualsiasi altro) è un problema multiforme. Il passo più importante che chiunque può fare per assicurarsi che un sito sia sicuro è tenere a mente che nessun singolo processo o metodo è sufficiente per garantire che non accada nulla di male. Ma ci sono cose che puoi fare per aiutare. Uno di questi è stare all'erta, nel codice che scrivi e nel codice degli altri che distribuisci, per funzioni che possono avere conseguenze negative. In questo articolo tratteremo esattamente queste: funzioni che uno sviluppatore di WordPress dovrebbe pensare chiaramente prima di utilizzare.
Lo stesso WordPress fornisce una considerevole libreria di funzioni, alcune delle quali possono essere pericolose. Oltre a ciò, ci sono molte funzioni PHP che uno sviluppatore di WordPress (PHP) utilizzerà con una certa frequenza che possono essere pericolose se utilizzate.
Tutte queste funzioni hanno usi legittimi e sicuri, ma sono anche funzioni che possono rendere facile l'abuso del codice a scopo negativo. Tratteremo le cose che molto probabilmente sono la causa di una vulnerabilità di sicurezza e perché è necessario tenerle d'occhio. Per prima cosa esamineremo le funzioni PHP nel tuo codice che i malintenzionati possono utilizzare per il male, quindi parleremo delle funzioni PHP specifiche di WordPress che possono anche causare mal di testa.
Funzioni PHP a cui prestare attenzione
Come abbiamo detto, PHP contiene molte funzioni facili da usare. Alcune di queste funzioni sono famose per la facilità con cui le persone possono fare cose cattive con loro. Tutte queste funzioni hanno un uso corretto, ma fai attenzione a come le usi e a come fa l'altro codice che inserisci in un progetto.
Assumiamo che tu abbia già virgolette magiche e registri globali disattivati se la tua versione di PHP li supporta. Erano disattivati per impostazione predefinita in PHP 5 e versioni successive, ma potevano essere attivati per le versioni precedenti alla 5.4. Pochi host lo hanno permesso, ma non credo che un articolo sulla sicurezza PHP sia veramente completo senza includerli.
O una menzione che PHP 5.6 è l'ultima versione 5.x ancora con supporto per la sicurezza in corso. Dovresti esserci almeno tu. E dovresti avere un piano per passare a PHP 7 prima della fine del supporto 5.6 alla fine del 2018.
Dopodiché, avrai solo alcune funzioni rischiose da affrontare. Iniziare con…
extract
È una cattiva notizia, specialmente su $_POST
o simili
Fortunatamente, l'uso della funzione di extract
di PHP è in gran parte caduto in disgrazia. Il fulcro del suo utilizzo era che potevi eseguirlo su una matrice di dati e le sue coppie chiave-valore sarebbero diventate variabili viventi nel tuo codice. Così
$arr = array( 'red' => 5 ); extract($arr); echo $red; // 5
funzionerebbe. Questo è interessante, ma è anche molto pericoloso se stai estraendo $_GET
, $_POST
, ecc. In questi casi, essenzialmente stai ricreando tu stesso il problema register_globals
: un aggressore esterno può modificare facilmente i valori delle tue variabili aggiungendo una stringa di query o campo modulo. La soluzione migliore è semplice: non utilizzare extract
.
Se crei l'array che estrai tu stesso, non è specificamente un problema di sicurezza usare extract
e, in alcuni casi, può essere utile. Ma tutti i suoi usi hanno il problema di confondere i futuri lettori. La creazione di un array e la chiamata extract
creano più confusione rispetto alla semplice dichiarazione delle variabili. Quindi ti incoraggio a fare invece dichiarazioni manuali, a meno che non sia completamente irrealizzabile.
Se devi usare l' extract
sull'input dell'utente, dovresti sempre usare il flag EXTR_SKIP
. Questo ha ancora il problema di confusione, ma elimina la possibilità che i valori preimpostati possano essere modificati da un estraneo malintenzionato tramite una semplice stringa di query o la modifica del modulo Web. (Perché "salta" i valori già impostati.)
" eval
Is Evil" perché il codice arbitrario fa paura
eval
in PHP e praticamente in qualsiasi altro linguaggio che lo porti è sempre in cima a elenchi come questo. E per una buona ragione. La documentazione ufficiale di PHP su PHP.net lo dice francamente:
ATTENZIONE : Il costrutto del linguaggio eval() è molto pericoloso perché consente l'esecuzione di codice PHP arbitrario. Il suo utilizzo è quindi sconsigliato. Se hai verificato attentamente che non c'è altra opzione che usare questo costrutto, presta particolare attenzione a non passare i dati forniti dall'utente al suo interno senza prima convalidarlo adeguatamente.
eval
consente di eseguire qualsiasi stringa arbitraria nel programma come se fosse codice PHP. Ciò significa che è utile per la "meta-programmazione" in cui stai costruendo un programma che può creare esso stesso un programma. È anche molto pericoloso perché se consenti che fonti arbitrarie (come una casella di testo su una pagina Web) vengano passate immediatamente al tuo eval
di stringhe di valutazione, improvvisamente hai reso banalmente facile per un utente malintenzionato fare praticamente tutto ciò che PHP può fare sul tuo server. Ciò include, ovviamente, la connessione al database, l'eliminazione di file e qualsiasi altra cosa che qualcuno può fare quando SSH è inserito in una macchina. Questo non va bene.
Se devi usare eval
nel tuo programma, dovresti fare di tutto per assicurarti di non consentire che l'input arbitrario dell'utente venga passato al suo interno. E se devi consentire a utenti arbitrari l'accesso agli input per eval
, limita ciò che possono fare tramite una lista nera di comandi che non consenti loro di eseguire, o (meglio, ma molto più difficile da implementare) una whitelist che contiene solo i comandi che tu considera sicuro. Meglio ancora, consenti solo un piccolo numero di modifiche ai parametri specifici, come solo numeri interi convalidati.
Ma tieni sempre presente questa frase di Rasmus Lerdorf, il fondatore di PHP:
"Se `eval()` è la risposta, quasi sicuramente stai facendo la domanda sbagliata."
Variazioni Su eval
Esistono, oltre al noto eval
, una varietà di altri modi in cui PHP ha storicamente supportato stringhe valutate come codice. I due più rilevanti per gli sviluppatori di WordPress sono preg_replace
con il modificatore /e
e create_function
. Ognuno funziona in modo leggermente diverso e preg_replace
dopo PHP 5.5.0 non funziona affatto. (PHP 5.5 e versioni precedenti non ricevono più aggiornamenti di sicurezza ufficiali e quindi è meglio non utilizzarli.)
Anche i modificatori /e
in Regex sono "malvagi"
Se stai utilizzando PHP 5.4.xo inferiore, ti consigliamo di tenere d'occhio le chiamate preg_replace
di PHP che terminano con una e. Può sembrare:
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
$html = preg_replace( '( (.*?) )e', '" " . strtoupper("$2") . " "', $html );) // or $html = preg_replace( '~ (.*?) ~e', '" " . strtoupper("$2") . " "', $html );)
PHP offre vari modi per "recintare" la tua espressione regolare, ma la cosa principale a cui vuoi prestare attenzione è "e" come ultimo carattere del primo argomento di preg_replace
. Se è presente, stai effettivamente passando l'intero segmento trovato come argomento al tuo PHP inline e quindi lo stai eval
. Questo ha esattamente gli stessi problemi di eval
se lasci che l'input dell'utente entri nella tua funzione. Il codice di esempio può essere sostituito utilizzando invece preg_replace_callback
. Il vantaggio di questo è che hai scritto la tua funzione, quindi è più difficile per un utente malintenzionato cambiare ciò che viene valutato. Quindi dovresti scrivere quanto sopra come:
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
// uppercase headings $html = preg_replace_callback( '( (.*?) )', function ($m) { return " " . strtoupper($m[2]) . " "; }, $html );
Anche consentire all'input dell'utente di create_function
s è negativo...
PHP ha anche la funzione create_function
. Questo è ora deprecato in PHP 7.2, ma era molto simile a eval
e presentava gli stessi svantaggi di base: consente di trasformare una stringa (il secondo argomento) in PHP eseguibile. E aveva gli stessi rischi: è troppo facile per te dare accidentalmente a un cracker intelligente la possibilità di fare qualsiasi cosa sul tuo server se non sei stato attento.
Questo, se sei su PHP sopra 5.3, è ancora più facile da risolvere rispetto a preg_replace
. Puoi semplicemente creare la tua funzione anonima senza utilizzare stringhe come intermediari. Questo è sia più sicuro che più leggibile, almeno ai miei occhi.
assert
Is also eval
-Like
assert
non è una funzione che vedo utilizzare da molti sviluppatori PHP, in WordPress o al di fuori di esso. Il suo intento è per asserzioni molto leggere sulle precondizioni per il tuo codice. Ma supporta anche un'operazione di tipo eval
. Per questo motivo, dovresti diffidare di esso quanto lo sei di eval
. Anche le asserzioni basate su stringhe (il cuore del perché questo è negativo) sono deprecate in PHP 7.2, il che significa che dovrebbe essere meno preoccupante in futuro.
L'inclusione di file variabili è un modo per consentire eventualmente l'esecuzione incontrollata di PHP
Abbiamo esplorato abbastanza bene il motivo per cui eval
è negativo, ma qualcosa come include
o require($filename'.php')
può, specialmente dove $filename
è impostato da un valore controllabile dall'utente, essere altrettanto cattive notizie. Il motivo è leggermente diverso da eval
. I nomi dei file delle variabili vengono spesso utilizzati per cose come il semplice routing da URL a file in app PHP non WordPress. Ma potresti vederli usati anche in WordPress.
Il cuore del problema è che quando include
o require
(o include_once
o require_once
) stai facendo eseguire al tuo script il file incluso. Sta, più o meno, eval
concettualmente quel file, anche se raramente ci pensiamo in quel modo.
Se hai scritto tutti i file che la tua variabile include
potrebbe richiamare e hai considerato cosa sarebbe successo quando lo fa, stai bene. Ma è una cattiva notizia se non hai considerato cosa include
ing password.php
o wp-config.php
. È anche una cattiva notizia se qualcuno potesse aggiungere un file dannoso e quindi eseguire il tuo include
(anche se a quel punto probabilmente hai problemi più grandi).
La soluzione a questo non è però troppo difficile: l'hard-code include quando puoi. Quando non puoi, disponi di una whitelist (migliore) o di una blacklist di file che possono essere inclusi. Se i file nella whitelist (ovvero: hai verificato cosa fa quando vengono aggiunti), saprai di essere al sicuro. Se non è nella tua whitelist, il tuo script non lo includerà. Con quella semplice modifica, sei abbastanza sicuro. Una whitelist sarebbe simile a questa:
$white_list = ['db.php', filter.php', 'condense.php'] If (in_array($white_list, $file_to_include)) { include($file_to_include); }
Non passare mai l'input dell'utente a shell_exec
e varianti
Questo è grande. shell_exec
, system
, exec
e backticks in PHP consentono tutti al codice in esecuzione di comunicare con la shell sottostante (di solito Unix). Questo è simile a ciò che rende eval
pericoloso ma raddoppiato. Raddoppiato perché se lasci passare l'input dell'utente qui con noncuranza, l'attaccante non è nemmeno vincolato dai vincoli di PHP.
La possibilità di eseguire comandi shell da PHP può essere molto utile come sviluppatore. Ma se si lascia entrare l'input di un utente, hanno la capacità di ottenere di nascosto molti poteri pericolosi. Quindi direi che l'input dell'utente non dovrebbe mai essere passato alle funzioni di tipo shell_exec
.
Il modo migliore in cui potrei pensare di gestire questo tipo di situazione, se fossi tentato di implementarlo, sarebbe fornire agli utenti l'accesso a un piccolo insieme di comandi shell predefiniti noti e sicuri. Potrebbe essere possibile proteggerlo. Ma anche allora ti consiglierei di stare molto attento.
Guarda per unserialize
; Esegue il codice automaticamente
L'azione principale di chiamare serialize
su un oggetto PHP vivente, archiviare quei dati da qualche parte e quindi utilizzare quel valore memorizzato in un secondo momento per unserialize
la serializzazione dell'oggetto in vita è interessante. È anche abbastanza comune, ma può essere rischioso. Perché rischioso? Se l'input di quella chiamata unserialize
non è completamente sicuro (diciamo che è memorizzato come cookie anziché nel tuo database...), un utente malintenzionato può modificare lo stato interno del tuo oggetto in un modo che rende la chiamata unserialize
qualcosa di negativo.
Questo exploit è più esoterico e meno probabile che venga notato rispetto a un problema di eval
. Ma se stai usando un cookie come meccanismo di archiviazione per i dati serializzati, non usare serialize
per quei dati. Usa qualcosa come json_encode
e json_decode
. Con quei due PHP non eseguirà mai automaticamente alcun codice.
La vulnerabilità principale qui è che quando PHP annulla la __wakeup
unserialize
quella classe. Se l'input dell'utente non convalidato può essere non unserialized
, qualcosa come una chiamata al database o l'eliminazione di file nel metodo __wakeup
potrebbe potenzialmente essere indirizzato a una posizione pericolosa o indesiderabile.
unserialize
è distinto dalle vulnerabilità eval
perché richiede l'uso di metodi magici sugli oggetti. Invece di creare il proprio codice, l'attaccante è costretto ad abusare dei metodi già scritti su un oggetto. Anche i metodi magici __destruct
e __toString
sugli oggetti sono rischiosi, come spiega questa pagina Wiki di OWASP.
In generale, sei a posto se non usi i __wakeup
, __destruct
o __toString
nelle tue classi. Ma poiché in seguito potresti vedere qualcuno aggiungerli a una classe, è una buona idea non lasciare mai che un utente si avvicini alle tue chiamate per serialize
e unserialize
la serializzazione e passare tutti i dati pubblici per quel tipo di utilizzo attraverso qualcosa come JSON ( json_encode
e json_decode
) dove c'è non è mai l'esecuzione automatica del codice.
Recuperare URL con file_get_contents
è rischioso
Una pratica comune quando si scrive rapidamente del codice PHP che deve chiamare un URL esterno è raggiungere file_get_contents
. È veloce, è facile, ma non è super sicuro.
Il problema con file_get_contents
è sottile, ma è abbastanza comune che gli host a volte configurino PHP per non consentirti nemmeno di accedere a URL esterni. Questo ha lo scopo di proteggerti.
Il problema qui è che file_get_contents
le pagine remote per te. Ma quando lo fa, non controlla l'integrità della connessione del protocollo HTTPS. Ciò significa che il tuo script potrebbe essere potenzialmente vittima di un attacco man-in-the-middle che consentirebbe a un utente malintenzionato di inserire praticamente tutto ciò che vuole nel risultato della tua pagina file_get_contents
.
Questo è un attacco più esoterico. Ma per proteggermi quando scrivo PHP moderno (basato su Composer), uso quasi sempre Guzzle per avvolgere l'API cURL più sicura. In WordPress è ancora più semplice: usa wp_remote_get
. Funziona in modo molto più coerente rispetto a file_get_contents
e per impostazione predefinita verifica le connessioni SSL. (Puoi disattivarlo, ma, ehm, forse no...) Meglio ancora, ma leggermente più fastidiosi di cui parlare, sono wp_safe_remote_get
, ecc. Funzionano in modo identico alle funzioni senza safe_
nel loro nome, ma faranno assicurati che reindirizzamenti e inoltri non sicuri non avvengano lungo il percorso.
Non fidarti ciecamente della convalida dell'URL da filter_var
Quindi questo è un po' oscuro, quindi complimenti a Chris Weigman per averlo spiegato in questo discorso di WordCamp. In generale, filter_var
di PHP è un ottimo modo per convalidare o disinfettare i dati. (Anche se, non confonderti su cosa stai cercando di fare...)
Il problema qui è piuttosto specifico: se stai cercando di usarlo per assicurarti che un URL sia sicuro, filter_var
non convalida il protocollo. Questo non è spesso un problema, ma se stai passando l'input dell'utente a questo metodo per la convalida e stai usando FILTER_VALIDATE_URL
, qualcosa come javascript://comment%0aalert(1)
passerà. Cioè, questo può essere un ottimo vettore per un attacco XSS di base in un posto che non ti aspetteresti.
Per la convalida di un URL, la funzione esc_url
di WordPress avrà un impatto simile, ma lascia passare solo i protocolli consentiti. javascript
non è nell'elenco predefinito, quindi ti manterrebbe al sicuro. Tuttavia, a differenza filter_var
, restituirà una stringa vuota (non falsa) per un protocollo non consentito che gli viene passato.
Funzioni specifiche di WordPress da tenere d'occhio
Oltre alle funzioni di base di PHP potenzialmente vulnerabili, ci sono alcune funzioni specifiche di WordPress che possono essere un po' complicate. Alcuni di questi sono molto simili alla varietà di funzioni pericolose sopra elencate, altri leggermente differenti.
WordPress annulla la serializzazione con maybe_unserialize
Questo è probabilmente ovvio se leggi quanto sopra. In WordPress c'è una funzione chiamata maybe_unserialize
e, come puoi immaginare, annulla la serializzazione di ciò che gli è passato, se necessario.
Non c'è nessuna nuova vulnerabilità che questo introduce, il problema è semplicemente che, proprio come la funzione di annullamento della unserialize
del core, questa può causare lo sfruttamento di un oggetto vulnerabile quando non è serializzato.
is_admin
non risponde se un utente è un amministratore!
Questo è piuttosto semplice, ma la funzione è ambigua nel nome, quindi è incline a confondere le persone o a mancare se sei di fretta. Dovresti sempre verificare che un utente che sta tentando di eseguire un'azione in WordPress disponga dei diritti e dei privilegi necessari per eseguire quell'azione. Per fare ciò, dovresti usare la funzione current_user_can
.
Ma potresti, erroneamente, pensare che is_admin
ti dirà se l'utente corrente è un account a livello di amministratore e quindi dovrebbe essere in grado di impostare un'opzione utilizzata dal tuo plug-in. Questo è un errore. Ciò che is_admin
fa in WordPress è invece dirti se il caricamento della pagina corrente è sul lato amministrativo del sito (rispetto al lato anteriore). Quindi ogni utente che può accedere a una pagina di amministrazione (come la "Dashboard") potrà potenzialmente superare questo controllo. Finché ricordi che is_admin
riguarda il tipo di pagina, non l'utente corrente, andrà tutto bene.
add_query_arg()
non disinfetta gli URL
Questo non è così comune, ma c'è stata una grande ondata di aggiornamenti nell'ecosistema di WordPress alcuni anni fa perché la documentazione pubblica su queste funzioni non era corretta. Il problema principale è che la funzione add_query_arg
(e il suo inverso remove_query_arg
) non disinfetta automaticamente l'URL del sito se non gli è stato passato un URL e la gente pensava che lo facesse. Molti plugin erano stati fuorviati dal Codex e di conseguenza lo stavano usando in modo non sicuro.
La cosa fondamentale che dovevano fare è diversa: sanificare il risultato di una chiamata a questa funzione prima di utilizzarla. Se lo fai, sei davvero al sicuro dagli attacchi XSS che pensavi fossero. Quindi sembra qualcosa del tipo:
echo esc_url( add_query_arg( 'foo', 'bar' ) );
$wpdb->query()
è aperto all'attacco SQL Injection
Se conosci l'iniezione di SQL, questo può sembrare sciocco, persino superfluo da elencare. Perché il fatto è che in qualsiasi modo si accede a un database (ad esempio utilizzando i driver di database mysqli
o PDO di PHP) per creare query di database che consentono attacchi di SQL injection.
Il motivo per cui sto chiamando specificamente $wpdb->query
è che alcuni altri metodi (come insert
, delete
, ecc.) su $wpdb
si prendono cura degli attacchi di injection per te. Inoltre, se sei abituato a eseguire query di base sui database di WordPress con WP_Query
o simili, non avrai bisogno di considerare l'iniezione di SQL. Questo è il motivo per cui lo sto chiamando: per assicurarti di comprendere un attacco injection al database è possibile quando provi per la prima volta a utilizzare $wpdb
per creare la tua query.
Cosa fare? Usa $wpdb->prepare()
e poi $wpdb->query()
. Dovrai anche assicurarti di prepararti prima di altri metodi di "ottenimento" di $wpdb
come get_row()
e get_var()
. Altrimenti Bobby Tables potrebbe prenderti.
esc_sql
non ti protegge nemmeno dall'iniezione di SQL
Per la maggior parte degli sviluppatori WordPress, direi che esc_sql
non si registra con alcun significato, ma dovrebbe. Come abbiamo appena detto, dovresti usare wpdb->prepare()
prima di eseguire qualsiasi query sul database. Questo ti terrà al sicuro. Ma è allettante e comprensibile che uno sviluppatore possa invece raggiungere esc_sql
. E potrebbero aspettarsi che sarebbe sicuro.
Il problema è che esc_sql
non ha protezioni robuste contro SQL injection. È davvero una versione glorificata della funzione add_slashes
di PHP, che sei stato scoraggiato dall'utilizzare per proteggere il tuo database per anni.
C'è altro da fare, ma questo è un grande inizio
C'è molto di più nella sicurezza del semplice essere alla ricerca di funzioni nel codice che gli aggressori possono utilizzare in modo improprio. Ad esempio, non abbiamo discusso in modo approfondito la necessità di convalidare e disinfettare tutti i dati che ricevi dagli utenti ed evitarli prima di inserirli in una pagina Web (anche se di recente ho pubblicato un articolo su quell'argomento, "Protezione del tuo WordPress Sito contro un attacco di scripting tra siti"). Ma puoi e dovresti usarlo come parte di una strategia di sicurezza più ampia.
Tenere questo elenco di funzioni facili da utilizzare in modo improprio al tuo fianco mentre esegui un'ispezione finale prima di distribuire un nuovo plug-in in WordPress è un'ottima idea. Ma vorrai anche assicurarti di fidarti della sicurezza del tuo hosting, assicurarti che i tuoi utenti dispongano di buone password e molto altro.
La cosa fondamentale da ricordare sulla sicurezza è che il tuo anello più debole è quello che conta. Le buone pratiche in un'area rafforzano i possibili punti deboli da qualche altra parte, ma non possono mai correggerlo del tutto. Ma fai attenzione a queste funzioni e avrai un vantaggio sulla maggior parte delle volte.