Tabelle database personalizzate sicurezza prima di tutto

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;

Disinfezione delle query del database

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 galleggiante

Iniziamo osservando tre metodi che non solo disinfettano le query, ma li costruiscono anche per te.

Inserimento di dati

WordPress fornisce il metodo $ Wpdb-> inserire (). È un wrapper per l'inserimento di dati nel database e gestisce la sanificazione. Ci vogliono tre parametri:

  • Nome della tabella - il nome del tavolo
  • Dati - matrice di dati da inserire come colonne> coppie di valori
  • formati - matrice di formati per il valore corrispondente nell'array di dati (ad es. %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

Aggiornamento dei dati

Per l'aggiornamento dei dati nel database che abbiamo $ Wpdb-> update (). Questo metodo accetta cinque argomenti:

  • Nome della tabella - il nome del tavolo
  • Dati - matrice di dati da aggiornare come coppie colonna-> valore
  • Dove - matrice di dati da abbinare a coppie colonna-> valore
  • Formato dei dati - serie di formati per i corrispondenti valori "dati"
  • Dove Format - matrice di formati per i corrispondenti valori "dove"

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

Eliminazione

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:

  • Nome della tabella - il nome del tavolo
  • Dove - matrice di dati da abbinare a coppie colonna-> valore
  • formati - matrice di formati per il tipo di valore corrispondente (ad es. %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

esc_sql

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)." ";";

Query generali

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.

  1. Si applica 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.
  2. Si applica 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'.

Altre query complicate

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.

whitelisting

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');

LIKE Queries

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);

Funzioni di Wrapper di query

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".


Sommario

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.