Questa è la seconda parte di una serie sulle tabelle di database personalizzate in WordPress. Nella prima parte abbiamo spiegato i motivi e contro l'utilizzo di tabelle personalizzate. Abbiamo esaminato alcuni dei dettagli che avrebbero dovuto essere considerati - denominazione delle colonne, tipi di colonne - e come creare la tabella. Prima di andare oltre, dobbiamo capire come interagire con questo nuovo tavolo tranquillamente. In un precedente articolo ho trattato la sanificazione generale e la convalida - in questo tutorial vedremo questo in modo più dettagliato nel contesto dei database.
La sicurezza quando si interagisce con una tabella di database è di primaria importanza, motivo per cui la stiamo esaminando all'inizio della serie. Se non eseguito correttamente, è possibile lasciare la tabella aperta alla manipolazione tramite SQL injection. Potrebbe consentire a un hacker di estrarre informazioni, sostituire contenuti o persino alterare il modo in cui il sito si comporta e il danno che potrebbero fare non è limitato alla tabella personalizzata.
Supponiamo di voler consentire agli amministratori di cancellare i record dal nostro registro delle attività. Un errore comune che ho visto è il seguente:
if (! empty ($ _ GET ['action']) && 'delete-activity-log' == $ _GET ['action'] && isset ($ _ GET ['log_id'])) globale $ wpdb; unsafe_delete_log ($ _ GET [ 'log_id']); function unsafe_delete_log ($ log_id) global $ wpdb; $ sql = "DELETE FROM $ wpdb-> wptuts_activity_log WHERE log_id = $ log_id"; $ deleted = $ wpdb-> query ($ sql);
Quindi cosa c'è di sbagliato qui? Molto: non hanno controllato le autorizzazioni, quindi chiunque può cancellare un registro delle attività. Né hanno controllato nozioni, quindi anche con i controlli delle autorizzazioni, un utente amministratore potrebbe essere indotto a eliminare un registro. Questo è stato tutto trattato in questo tutorial. Ma il loro terzo errore comprende i primi due: il unsafe_delete_log ()
la funzione utilizza il valore passato in un comando SQL senza prima eseguire l'escape. Questo lo lascia completamente aperto alla manipolazione.
Supponiamo che la sua destinazione d'uso sia
www.unsafe-site.com?action=delete-activity-log&log_id=7
Che cosa succede se un utente malintenzionato ha visitato (o ingannato un amministratore per visitarlo): www.unsafe-site.com?action=delete-activity-log&log_id=1;%20DROP%20TABLE%20wp_posts
. Il log_id
contiene un comando SQL, che viene successivamente iniettato in $ sql
e sarebbe eseguito come:
DELETE da wp_wptuts_activity_log WHERE log_id = 1; DROP TABLE wp_posts
Il risultato: l'intero wp_posts
la tabella è cancellata. Ho visto codice come questo sui forum e il risultato è che chiunque visita il loro sito può aggiornarlo o cancellarlo qualunque tabella nel loro database.
Se i primi due errori sono stati corretti, rende più difficile il funzionamento di questo tipo di attacco, ma non impossibile, e non protegge da un "utente malintenzionato" che ha il permesso di eliminare i registri delle attività. È incredibilmente importante proteggere il tuo sito contro le iniezioni SQL. È anche incredibilmente semplice: WordPress fornisce il preparare
metodo. In questo esempio particolare:
function safe_delete_log ($ log_id) global $ wpdb; $ sql = $ wpdb-> prepare ("DELETE da $ wpdb-> wptuts_activity_log WHERE log_id =% d", $ log_id); $ deleted = $ wpdb-> query ($ sql)
Il comando SQL ora verrebbe eseguito come
DELETE da wp_wptuts_activity_log WHERE log_id = 1;
La maggior parte della sanificazione può essere eseguita utilizzando esclusivamente il $ wpdb
globale - in particolare attraverso il suo preparare
metodo. Fornisce inoltre metodi per l'inserimento e l'aggiornamento sicuro dei dati nelle tabelle. Questi di solito funzionano sostituendo un input sconosciuto o associando un input con un segnaposto di formato. Questo formato indica a WordPress quali dati è da aspettarsi:
%S
denota una stringa% d
denota un numero intero % f
denota un galleggianteIniziamo osservando tre metodi che non solo disinfettano le query, ma li costruiscono anche per te.
WordPress fornisce il metodo $ Wpdb-> inserire ()
. È un wrapper per l'inserimento di dati nel database e gestisce la sanificazione. Ci vogliono tre parametri:
%S
, % d
,% f
)Si noti che le chiavi dei dati devono essere colonne: se c'è una chiave che non corrisponde a una colonna, può essere generato un errore.
Negli esempi che seguono abbiamo impostato in modo esplicito i dati, ma ovviamente, in generale, questi dati sarebbero derivati dall'input dell'utente, quindi potrebbe essere qualsiasi cosa. Come discusso in questo articolo i dati dovrebbero sono stati prima convalidati, in modo da restituire eventuali errori all'utente, ma dobbiamo ancora disinfettare i dati prima di aggiungerli alla nostra tabella. Esamineremo la convalida nel prossimo articolo di questa serie.
globale $ wpdb; // $ user_id = 1; $ attività = 1; $ object_id = 1479; $ activity_date = date_i18n ('Y-m-d H: i: s', false, true); $ inserted = $ wpdb-> insert ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity, 'object_id' => $ object_id, 'activity_date' => $ activity_date,) , array ('% d', '% s', '% d', '% s',)); se ($ inserito) $ insert_id = $ wpdb-> insert_id; else // Insert failed
Per l'aggiornamento dei dati nel database che abbiamo $ Wpdb-> update ()
. Questo metodo accetta cinque argomenti:
Questo aggiorna tutte le righe che corrispondono all'array where con i valori dell'array di dati. Di nuovo, come con $ Wpdb-> inserire ()
le chiavi dell'array di dati devono corrispondere a una colonna. Ritorna falso
in caso di errore, o il numero di righe aggiornate.
Nell'esempio seguente aggiorniamo tutti i record con l'ID del registro '14' (che dovrebbe essere al massimo un record, poiché questa è la nostra chiave primaria). Aggiorna l'ID utente a 2 e l'attività a "modificato".
globale $ wpdb; $ User_id = 2; $ Attività = 'modificato'; $ log_id = 14; $ updated = $ wpdb-> update ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity,), array ('log_id' => $ log_id,), array (' % d ','% s '), array ('% d '),); se ($ aggiornato) // Numero di righe aggiornate = $ aggiornato
Dal 3,4 WordPress ha anche fornito il $ Wpdb-> delete ()
metodo per cancellare facilmente (e in sicurezza) una o più righe. Questo metodo richiede tre parametri:
%S
, % d
,% f
)Se vuoi che il tuo codice sia compatibile con WordPress pre-3.4, allora dovrai usare il $ Wpdb-> preparare
metodo per disinfettare l'istruzione SQL appropriata. Un esempio di questo è stato dato sopra. Il $ Wpdb-> cancellare
metodo restituisce il numero di righe eliminate o false altrimenti - in modo da poter determinare se l'eliminazione è avvenuta correttamente.
globale $ wpdb; $ deleted = $ wpdb-> delete ($ wpdb-> wptuts_activity_log, array ('log_id' => 14,), array ('% d'),); se ($ eliminato) // Numero di righe cancellate = $ cancellato
Alla luce dei metodi sopra, e il più generale $ Wpdb-> prepare ()
metodo discusso dopo, questa funzione è un po 'ridondante. È fornito come un utile wrapper per $ Wpdb-> escape ()
metodo, a sua volta un glorificato addslashes
. Poiché è solitamente più appropriato e consigliabile, utilizzare i tre metodi precedenti, o $ Wpdb-> prepare ()
, probabilmente troverai che hai raramente bisogno di usare esc_sql ()
.
Come un semplice esempio:
$ activity = 'commented'; $ sql = "DELETE FROM $ wpdb-> wptuts_activity_log WHERE.esc_sql ($ activity)." ";";
Per i comandi SQL generali dove (cioè quelli che non inseriscono, rimuovono o aggiornano le righe) dobbiamo usare il metodo $ Wpdb-> prepare ()
. Accetta un numero variabile di argomenti. La prima è la query SQL che desideriamo eseguire con tutti i dati "sconosciuti" sostituiti dal loro segnaposto in formato appropriato. Questi valori vengono passati come argomenti aggiuntivi, nell'ordine in cui vengono visualizzati.
Ad esempio invece di:
$ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id = $ user_id AND object_id = $ object_id AND activity = $ activity ORDER BY activity_date $ order"; $ logs = $ wpdb-> get_results ($ sql);
noi abbiamo
$ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id =% d AND object_id =% d AND activity =% s ORDER BY activity_date% s", $ user_id, $ object_id, $ activity , $ ordine); $ logs = $ wpdb-> get_results ($ sql);
Il preparare
il metodo fa due cose.
mysql_real_escape_string ()
(o addslashes ()
) ai valori che si stanno inserendo. In particolare ciò impedirà che i valori contenenti virgolette saltino fuori dalla query. vsprintf ()
quando si aggiungono i valori alla query per assicurarsi che siano formattati in modo appropriato (quindi gli interi sono numeri interi, i float sono float ecc.). Questo è il motivo per cui il nostro esempio all'inizio dell'articolo ha messo a nudo tutto tranne l''1'. Dovresti trovarlo $ Wpdb-> preparare
, insieme con l'inserimento, i metodi di aggiornamento e cancellazione sono tutto ciò di cui hai veramente bisogno. A volte però ci sono circostanze in cui si desidera un approccio più 'manuale' - a volte solo dal punto di vista della leggibilità. Ad esempio, supponiamo di avere una serie sconosciuta di attività per le quali vogliamo tutti i registri. Noi * potremmo * aggiungere dinamicamente il %S
segnaposto alla query SQL, ma un approccio più diretto sembra più semplice:
// Un array sconosciuto che dovrebbe contenere stringhe interrogate per $ activities = array (...); // Disinfetta il contenuto dell'array $ activities = array_map ('esc_sql', $ activities); $ activities = array_map ('sanitize_title_for_query', $ activities); // Crea una stringa dall'array sanitizzato che forma la parte interna dell'istruzione IN (...) $ in_sql = "'". implode ("','", $ attività). "'"; // Aggiungi questo alla query $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE attività IN ($ in_sql);" // Esegue la query $ logs = $ wpdb-> get_results ($ sql);
L'idea è di applicare esc_sql
e sanitize_title_for_query
a ciascun elemento dell'array. Il primo aggiunge barre per sfuggire ai termini - simile a cosa $ Wpdb-> prepare ()
lo fa. Il secondo si applica semplicemente sanitize_title_with_dashes ()
- sebbene il comportamento possa essere completamente modificato attraverso i filtri. L'attuale istruzione SQL si forma implodendo l'array ora sanitizzato in una stringa separata da virgole, che viene aggiunta nel file NEL(… )
parte della query.
Se ci si aspetta che l'array contenga numeri interi, è sufficiente utilizzarlo intval ()
o absint ()
per disinfettare ogni elemento dell'array.
In altri casi la whitelisting potrebbe essere appropriata. Ad esempio l'input sconosciuto può essere una matrice di colonne che devono essere restituite nella query. Poiché sappiamo quali sono le colonne del database, possiamo semplicemente autorizzarle in whitelist, rimuovendo tutti i campi che non riconosciamo. Tuttavia, per rendere il nostro codice umano amichevole dovremmo essere insensibili al maiuscolo / minuscolo. Per fare questo convertiamo tutto ciò che riceviamo in minuscolo, poiché nella prima parte abbiamo usato in particolare i nomi di colonne in minuscolo.
// Un array sconosciuto che dovrebbe contenere colonne da includere nella query $ fields = array (...); // Una whitelist di campi consentiti $ allowed_fields = array (...); // Converti i campi in minuscolo (poiché i nomi delle nostre colonne sono tutti in minuscolo - vedi la parte 1) $ fields = array_map ('strtolower', $ fields); // Disinfetta per elenco bianco $ fields = array_intersect ($ fields, $ allowed_fields); // Restituisce solo i campi selezionati. I campi $ vuoti sono interpretati come tutti if (vuoto ($ campi)) $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log"; else $ sql = "SELECT" .implode (',', $ fields). "FROM $ wpdb-> wptuts_activity_log"; // Esegue la query $ logs = $ wpdb-> get_results ($ sql);
La whitelist è comoda anche quando si imposta il ORDINATO DA
parte della query (se questa è impostata dall'input dell'utente): i dati possono essere ordinati come DESC
o ASC
solo.
// input utente sconosciuto (che dovrebbe essere asc o desc) $ order = $ _GET ['order']; // Consenti all'input di essere qualsiasi, o misto, caso $ order = strtoupper ($ order); // Valore dell'ordine sanificato $ order = ('ASC' == $ order? 'ASC': 'DEC');
Le istruzioni SQL LIKE supportano l'uso di caratteri jolly come %
(zero o più caratteri) e _
(esattamente un carattere) quando si confrontano i valori con la query. Ad esempio il valore foobar
corrisponderebbe a qualsiasi domanda:
SELECT * FROM $ wpdb-> wptuts_activity_log WHERE attività LIKE 'foo%' SELECT * FROM $ wpdb-> wptuts_activity_log WHERE attività LIKE '% bar' SELECT * FROM $ wpdb-> wptuts_activity_log WHERE attività LIKE '% oba%' SELEZIONA * DA $ wpdb-> wptuts_activity_log WHERE attività LIKE 'fo_bar%'
Tuttavia, questi caratteri speciali potrebbero effettivamente essere presenti nel termine cercato - e quindi per impedire che vengano interpretati come caratteri jolly - abbiamo bisogno di sfuggirli. Per questo WordPress fornisce il like_escape ()
funzione. Si noti che ciò non impedisce l'iniezione SQL, ma sfugge solo a %
e _
personaggi: hai ancora bisogno di usare esc_sql ()
o $ Wpdb-> prepare ()
.
// Raccogli termini $ termine = $ _GET ['attività']; // Elimina qualsiasi carattere jolly $ term = like_escape ($ term); $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE attività LIKE% s", '%'. $ term. '%'); $ logs = $ wpdb-> get_results ($ sql);
Negli esempi che abbiamo visto abbiamo usato altri due metodi di $ wpdb
:
$ wpdb-> query ($ sql)
- Questo esegue qualsiasi query assegnata e restituisce il numero di righe interessate.$ wpdb-> get_results ($ sql, $ ouput)
- Questo esegue la query assegnata e restituisce il set di risultati corrispondente (cioè le righe corrispondenti). $ uscita
imposta il formato dei risultati restituiti: ARRAY_A
- matrice numerica di righe, in cui ogni riga è un array associativo, digitato dalle colonne.ARRAY_N
- matrice numerica di righe, in cui ogni riga è una matrice numerica.OGGETTO
- matrice numerica di righe, dove ogni riga è un oggetto riga. Predefinito.OBJECT_K
- array associativo di righe (definito dal valore della prima colonna), dove ogni riga è un array associativo.Ce ne sono altri che non abbiamo menzionato:
$ wpdb-> get_row ($ sql, $ ouput, $ row)
- Questo esegue la query e restituisce una riga. $ row
imposta quale riga deve essere restituita, di default è 0, la prima riga corrispondente. $ uscita
imposta il formato della riga: ARRAY_A
- Row è un colonna => Valore
paio.ARRAY_N
- La riga è una matrice numerica di valori.OGGETTO
- La riga viene restituita come oggetto. Predefinito.$ wpdb-> get_col ($ sql, $ column)
- Questo esegue la query e restituisce una matrice numerica di valori dalla colonna specificata. $ colonna
specifica quale colonna restituire come intero. Di default questo è 0, la prima colonna. $ wpdb-> get_var ($ sql, $ column, $ row)
- Questo esegue la query e restituisce un valore particolare. $ row
e $ colonna
come sopra, e specificare quale valore restituire. Per esempio, $ activities_by_user_1 = $ wpdb-> get_var ("SELECT COUNT (*) FROM $ wpdb-> wptuts_activity_log WHERE user_id = 1");
È importante notare che questi metodi sono solo wrapper per eseguire una query SQL e formattare il risultato. Non sanificano la query - quindi non dovresti usarli da soli quando la query contiene alcuni dati "sconosciuti".
In questo tutorial abbiamo affrontato molte questioni e la sanificazione dei dati è un argomento importante da comprendere. Nel prossimo articolo lo applicheremo al nostro plug-in. Osserveremo lo sviluppo di una serie di funzioni wrapper (simili a funzioni come wp_insert_post ()
, wp_delete_post ()
ecc.) che aggiungerà uno strato di astrazione tra il nostro plug-in e il database.