Tabele de baze de date personalizate siguranța în primul rând

Aceasta este partea a doua a unei serii despre tabelele de baze de date personalizate în WordPress. În prima parte am acoperit motivele pentru și împotriva tabelelor personalizate. Am analizat unele detalii care ar trebui luate în considerare - denumirea coloanelor, tipurile de coloane - precum și modul de creare a tabelului. Înainte de a merge mai departe, trebuie să acoperim modul de a interacționa cu această nouă masă în siguranță. Într-un articol precedent am abordat sanitația generală și validarea - în acest tutorial vom examina acest lucru mai detaliat în contextul bazelor de date.

Siguranța atunci când interacționați cu o tabelă de baze de date este de o importanță capitală - de aceea o acoperim mai devreme în serie. Dacă nu ați făcut corect, atunci puteți lăsa masa deschisă la manipulare prin injectarea SQL. Ar putea permite unui hacker să extragă informații, să înlocuiască conținutul sau chiar să modifice modul în care se comportă site-ul dvs. - iar pagubele pe care le-ar putea face nu se limitează la masa personalizată.

Să presupunem că vrem să permitem administratorilor să șterge înregistrări din jurnalul nostru de activități. O greșeală comună pe care am văzut-o este următoarea:

 dacă empty $ _GET ['action']) && 'delete-activity-log' == $ _GET ['action'] && isset ($ _GET ['log_id'])) global $ wpdb; unsafe_delete_log ($ _ GET [ 'log_id']);  funcția unsafe_delete_log ($ log_id) global $ wpdb; $ sql = "Șterge din $ wpdb-> wptuts_activity_log WHERE log_id = $ log_id"; $ deleted = $ wpdb-> interogare ($ sql); 

Deci, ce e în neregulă aici? O mulțime: Nu au verificat permisiunile, astfel încât oricine poate șterge un jurnal de activități. Nici nu au verificat noncesurile, deci chiar și cu verificările de permisiune, un utilizator de administrare ar putea fi înșelat în ștergerea unui jurnal. Toate acestea au fost acoperite în acest tutorial. Dar a treia lor greșeală compune primele două: unsafe_delete_log () funcția utilizează valoarea transferată într-o comandă SQL fără a o scăpa mai întâi. Acest lucru lasă larg deschis pentru manipulare.

Să presupunem că se dorește utilizarea sa

 www.unsafe-site.com?action=delete-activity-log&log_id=7

Ce se întâmplă dacă un atacator a vizitat (sau a păcălit un administrator în vizită): www.unsafe-site.com?action=delete-activity-log&log_id=1;%20DROP%20TABLE%20wp_posts. log_id conține o comandă SQL, care este ulterior injectată $ sql și ar fi executat ca:

 DELETE de la wp_wptuts_activity_log Unde log_id = 1; TABEL DE DROP wp_posts

Rezultatul: întregul wp_posts tabelul este șters. Am văzut un cod ca acesta pe forumuri - și rezultatul este că oricine vizitează site-ul lor poate actualiza sau șterge orice în baza lor de date.

Dacă primele două greșeli au fost corectate, atunci este mai dificil ca acest tip de atac să funcționeze - dar nu imposibil, și nu ar proteja împotriva unui "atacator" care are permisiunea de a șterge jurnalele de activitate. Este incredibil de important să vă protejați site-ul împotriva injecțiilor SQL. Este, de asemenea, incredibil de simplu: WordPress oferă a pregati metodă. În acest exemplu particular:

 funcția safe_delete_log ($ log_id) global $ wpdb; $ sql = $ wpdb-> pregăti ("DELETE din $ wpdb-> wptuts_activity_log WHERE log_id =% d", $ log_id); $ deleted = $ wpdb-> interogare ($ sql)

Comanda SQL va fi executată acum ca

 DELETE de la wp_wptuts_activity_log Unde log_id = 1;

Satisfacerea interogărilor bazei de date

Majoritatea sanitizării poate fi efectuată exclusiv folosind $ wpdb global - în special prin intermediul acestuia a pregati metodă. De asemenea, oferă metode pentru introducerea și actualizarea datelor în tabele în siguranță. Acestea funcționează de obicei înlocuind o intrare necunoscută sau asociând o intrare cu un substituent format. Acest format spune WordPress ce date se așteaptă:

  • % s desemnează un șir
  • % d reprezintă un număr întreg
  • % f desemnează un flotor

Începem prin a privi trei metode care nu numai că dezinstalează interogările - ci și le construiți pentru dvs..

Introducerea datelor

WordPress oferă metoda $ Wpdb-> insera (). Este un pachet pentru introducerea datelor în baza de date și gestionează igiena. Este nevoie de trei parametri:

  • Numele tabelului - numele tabelului
  • Date - array de date pentru a introduce ca perechi de coloane-> valoare
  • formate - array de formate pentru valoarea corespunzătoare din matricea de date (de ex. % s, % d,% f)

Rețineți că cheile datelor trebuie să fie coloane: dacă există o cheie care nu se potrivește cu o coloană, ar putea fi aruncată o eroare.

În exemplele care urmează, am setat în mod explicit datele - dar, desigur, în general, aceste date ar fi provenit din contribuția utilizatorului - deci ar putea fi orice. Așa cum am discutat în acest articol, datele ar trebui să au fost validate mai întâi pentru a returna orice eroare utilizatorului - dar trebuie să dezinfectăm datele înainte de a le adăuga la masa noastră. Vom analiza validarea în următorul articol din această serie.

 global $ wpdb; // $ user_id = 1; $ activitate = 1; $ object_id = 1479; $ activity_date = date_i18n ('Y-m-d H: i: s', falsă, adevărată); $ insert = $ wpdb-> inserați ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity_date, 'activity_date' => $ activity_date) , matrice ("% d", "% s", "% d", "% s",)); dacă ($ introdus) $ insert_id = $ wpdb-> insert_id;  altceva // Insert failed

Actualizarea datelor

Pentru actualizarea datelor din baza de date avem $ Wpdb-> actualizare (). Această metodă acceptă cinci argumente:

  • Numele tabelului - numele tabelului
  • Date - array de date pentru actualizare ca perechi de coloane-> valoare
  • Unde - array de date pentru a se potrivi ca perechi de coloane-> valoare
  • Formatul datelor - array de formate pentru valorile corespunzătoare "datelor"
  • În cazul în care Format - set de formate pentru valorile corespunzătoare "unde"

Aceasta actualizează orice rânduri care se potrivesc cu array unde există valori din matricea de date. Din nou, ca și cu $ Wpdb-> insera () cheile matricei de date trebuie să se potrivească cu o coloană. Se întoarce fals pe eroare sau numărul de rânduri actualizate.

În următorul exemplu, actualizăm înregistrările cu ID-ul jurnalului "14" (care ar trebui să fie cel mult o înregistrare, deoarece aceasta este cheia noastră primară). Acesta actualizează ID-ul de utilizator la 2 și activitatea la 'edited'.

 global $ wpdb; $ User_id = 2; $ Activitate = 'editat'; $ log_id = 14; $ update = $ wpdb-> actualizare ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity, % d ','% s ​​'), matrice ('% d '),); dacă ($ actualizat) // Numărul rândurilor actualizate = $ actualizate

Ștergerea

Din moment ce WordPress 3.4 a furnizat de asemenea $ Wpdb-> șterge () pentru ștergerea cu ușurință (și în siguranță) a rândului (liniilor). Această metodă are trei parametri:

  • Numele tabelului - numele tabelului
  • Unde - array de date pentru a se potrivi ca perechi de coloane-> valoare
  • formate - set de formate pentru tipul de valoare corespunzător (de ex. % s, % d,% f)

Dacă doriți codul dvs. să fie compatibil cu WordPress pre-3.4, atunci va trebui să utilizați $ Wpdb-> pregăti de dezinfectare a instrucțiunii SQL corespunzătoare. Un exemplu de acest lucru a fost dat mai sus. $ Wpdb-> șterge metoda returnează numărul de rânduri șterse sau altfel false - pentru a determina dacă ștergerea a avut succes.

 global $ wpdb; $ deleted = $ wpdb-> ștergeți ($ wpdb-> wptuts_activity_log, array ('log_id' => 14,) array ('% d')); dacă ($ șters) // Numărul de rânduri șterse = $ șterse

esc_sql

Având în vedere metodele de mai sus și cele mai generale $ Wpdb-> pregăti () Metoda discutată în continuare, această funcție este puțin redundantă. Este furnizat ca un wrapper util pentru $ Wpdb-> evacuare () metodă, ea însăși glorificată addslashes. Deoarece, de obicei, este mai adecvat și recomandabil să se utilizeze cele trei metode de mai sus, sau $ Wpdb-> pregăti (), probabil veți găsi că rareori trebuie să utilizați esc_sql ().

Ca un exemplu simplu:

 $ activity = 'comentat'; $ sql = "DELETE FROM $ wpdb-> wptuts_activity_log WHERE.esc_sql (activitate $)." ";";

Întrebări generale

Pentru comenzile SQL generale unde (adică cele care nu introduc, elimină sau actualizează rânduri) trebuie să folosim metoda $ Wpdb-> pregăti (). Acceptă un număr variabil de argumente. Prima este interogarea SQL pe care dorim să o executăm cu toate datele "necunoscute" înlocuite de substituentul de format corespunzător al acestora. Aceste valori sunt transmise ca argumente suplimentare, în ordinea în care apar.

De exemplu, în loc de:

 $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id = $ user_id ȘI object_id = $ object_id ȘI activitate = $ activity ORDER BY activity_date $ order"; $ logs = $ wpdb-> get_results ($ sql);

noi avem

 $ sql = $ wpdb-> pregăti ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id =% d AND object_id =% d AND activitate =% s ORDER BY activity_date% s", $ user_id, $ object_id, $ activity , $ order); $ logs = $ wpdb-> get_results ($ sql);

a pregati metoda face două lucruri.

  1. Se aplică mysql_real_escape_string () (sau addslashes ()) la valorile introduse. În special, acest lucru va împiedica valorile care conțin ghilimele să iasă din interogare.
  2. Se aplică vsprintf () atunci când adăugați valorile la interogare pentru a vă asigura că acestea sunt formate corespunzător (deci întregi sunt numere întregi, plutitoare sunt flotoare etc.). Acesta este motivul pentru care exemplul nostru la începutul articolului a scos totul, dar "1".

Mai multe interogări complicate

Ar trebui să găsiți asta $ Wpdb-> pregăti, împreună cu metodele de inserare, actualizare și ștergere sunt tot ceea ce aveți cu adevărat nevoie. Uneori, totuși, există circumstanțe în care se dorește o abordare mai "manuală" - uneori doar din punctul de vedere al lizibilității. De exemplu, să presupunem că avem o gamă necunoscută de activități pentru care dorim toate buștenii. Am putut * adăuga dinamic % s placeholderi la interogarea SQL, dar o abordare mai directă pare mai ușoară:

 // O matrice necunoscută care ar trebui să conțină șiruri de căutat pentru $ activities = array (...); // Sanitizează conținutul matrice $ activities = array_map ('esc_sql', $ activities); $ activities = array_map ('sanitize_title_for_query', activități $); // Creați un șir din matricea dezinfectată care formează partea interioară a instrucțiunii IN (...) $ in_sql = "'". implode ("", "", $ activități). "'"; // adăugați la cererea $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activitate în ($ in_sql);" // Efectuați interogarea $ logs = $ wpdb-> get_results ($ sql);

Ideea este să se aplice esc_sql și sanitize_title_for_query la fiecare element din matrice. Primul adaugă slash-uri pentru a scăpa de termeni - similar cu ceea ce $ Wpdb-> pregăti () face. Al doilea este pur și simplu aplicabil sanitize_title_with_dashes () - deși comportamentul poate fi complet modificat prin filtre. Instrucțiunea SQL reală este formată prin implodarea matricei dezinfectate acum într-un șir separat prin virgulă, care este adăugat în IN (...) parte din interogare.

Dacă matricea este de așteptat să conțină întregi, atunci este suficientă utilizarea intval () sau absint () pentru a dezinfecta fiecare element din matrice.

lista albă

În alte cazuri, lista albă poate fi potrivită. De exemplu, intrarea necunoscută poate fi o matrice de coloane care urmează să fie returnată în interogare. Din moment ce știm care sunt coloanele bazei de date, le putem lăsa pur și simplu pe listă - eliminând orice câmpuri pe care nu le recunoaștem. Cu toate acestea, pentru a face codul nostru uman prietenos, ar trebui să fim insensibili la caz. Pentru a face acest lucru, vom transforma tot ce primim în litere mici - deoarece în prima parte am folosit în mod specific numele coloanelor cu litere mici.

 // O matrice necunoscută care ar trebui să conțină coloane care să fie incluse în interogarea $ fields = array (...); // O listă albă de câmpuri permisibile $ allowed_fields = array (...); // Convertiți câmpurile la litere mici (numele coloanelor noastre sunt cu litere mici - vedeți partea 1) $ fields = array_map ('strtolower', câmpuri $); // Sanitizează prin listarea albă $ fields = array_intersect ($ fields, $ allowed_fields); // Reveniți numai câmpurile selectate. Golurile câmpurilor $ sunt interpretate ca toate în cazul în care (goluri (câmpuri $)) $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log";  altceva $ sql = "SELECT" .implode (',', câmpuri $) "FROM $ wpdb-> wptuts_activity_log";  // Efectuați interogarea $ logs = $ wpdb-> get_results ($ sql);

Lista albă este, de asemenea, convenabilă atunci când setați COMANDA DE LA parte a interogării (dacă aceasta este setată de intrarea utilizatorului): datele pot fi ordonate ca DESC sau ASC numai.

 // Intrare necunoscută a utilizatorului (se așteaptă să fie asc sau desc) $ order = $ _GET ['order']; // Permite intrarea să fie orice, sau amestecat, cazul $ order = strtoupper ($ order); // Valoare ordonată sanitară $ order = ('ASC' == $ comanda? 'ASC': 'DEC');

LIKE Interogări

Exemplele SQL LIKE susțin utilizarea de metacaractere, cum ar fi % (zero sau mai multe caractere) și _ (exact un caracter) atunci când se potrivesc valorile cu interogarea. De exemplu, valoarea foobar ar potrivi oricare dintre interogări:

 SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activitate LIKE 'foo%' SELECT * FROM $ wpdb-> wptuts_activity_log WHERE Activitate LIKE '% bar' SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activitate LIKE '% oba%' SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activitate LIKE 'fo_bar%'

Cu toate acestea, aceste caractere speciale pot fi de fapt prezente în termenul căutat - și astfel pentru a le împiedica să fie interpretate ca metacaractere - trebuie să le scăpăm. Pentru acest WordPress oferă like_escape () funcţie. Rețineți că acest lucru nu împiedică injectarea SQL - ci doar scapă % și _ caractere: trebuie să mai utilizați esc_sql () sau $ Wpdb-> pregăti ().

 // Colectează termenul $ term = $ _GET ['activity']; // Evadează orice wildcards $ term = like_escape ($ term); $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activitate LIKE% s", '%'. $ termen. '%'); $ logs = $ wpdb-> get_results ($ sql);

Întrebări legate de Wrapper Functions

În exemplele pe care le-am privit, am folosit alte două metode $ wpdb:

  • $ wpdb-> interogare ($ sql) - Aceasta efectuează orice interogare dată și returnează numărul de rânduri afectate.
  • $ wpdb-> get_results ($ sql, $ ouput) - Aceasta efectuează interogarea care îi este dată și returnează setul de rezultate care se potrivește (adică rândurile de potrivire). $ ieșire stabilește formatul rezultatelor returnate:
    • ARRAY_A - seria numerică a rândurilor, în care fiecare rând este o matrice asociativă, tastată de coloane.
    • ARRAY_N - seria numerică a rândurilor, unde fiecare rând este o matrice numerică.
    • OBIECT - seria numerică de rânduri, în care fiecare rând este un obiect rând. Mod implicit.
    • OBJECT_K - rândul asociativ de rânduri (tastat de valoarea primei coloane), unde fiecare rând este o matrice asociativă.

Există și altele pe care nu le-am menționat niciodată:

  • $ wpdb-> get_row ($ sql, $ ouput, $ row) - Aceasta efectuează interogarea și returnează un rând. $ rând stabilește rândul care urmează să fie returnat, în mod implicit acesta este 0, primul rând de potrivire. $ ieșire stabilește formatul rândului:
    • ARRAY_A - Rândul este a coloană => valoare pereche.
    • ARRAY_N - Rândul este o matrice numerică de valori.
    • OBIECT - Rândul este returnat ca obiect. Mod implicit.
  • $ wpdb-> get_col ($ sql, coloană $) - Aceasta efectuează interogarea și returnează o matrice numerică de valori din coloana specificată. coloana $ specifică ce coloană să se întoarcă ca întreg. Implicit aceasta este 0, prima coloană.
  • $ wpdb-> get_var ($ sql, $ coloană, $ rând) - Aceasta efectuează interogarea și returnează o anumită valoare. $ rând și coloana $ sunt cele de mai sus și specificați care valoare să reveniți. De exemplu,
     $ activities_by_user_1 = $ wpdb-> get_var ("SELECT COUNT (*) FROM $ wpdb-> wptuts_activity_log WHERE user_id = 1");

Este important să rețineți că aceste metode sunt doar împachetări pentru efectuarea unei interogări SQL și formatarea rezultatului. Ei nu dezinfectă interogarea - astfel încât nu ar trebui să le utilizați singuri atunci când interogarea conține date "necunoscute".


rezumat

Am acoperit destul de mult în acest tutorial - iar sanitația datelor este un subiect important de înțeles. În următorul articol îl vom aplica în plug-in-ul nostru. Ne vom uita la dezvoltarea unui set de funcții de împachetare (similare cu funcții precum wp_insert_post (), wp_delete_post () etc), care vor adăuga un strat de abstractizare între plug-in-ul nostru și baza de date.

Cod