Tilpassede databasetabeller Sikkerhet først

Dette er del to i en serie om tilpassede databasetabeller i WordPress. I del ett dekket vi grunnene til og mot, ved hjelp av egendefinerte tabeller. Vi så på noen detaljer som måtte vurderes - kolonne navngivelse, kolonne typer - samt hvordan du lager bordet. Før vi går videre, må vi dekke hvordan vi skal samhandle med dette nye bordet trygt. I en tidligere artikkel dekket jeg generell sanitisering og validering - i denne veiledningen vil vi se nærmere på dette i forbindelse med databaser.

Sikkerhet når du samhandler med en databasetabell er viktig - derfor dekker vi det tidlig i serien. Hvis ikke gjort riktig, kan du la tabellen være åpen for manipulering via SQL-injeksjon. Det kan tillate en hacker å trekke ut informasjon, erstatte innhold eller til og med endre måten nettstedet ditt oppfører seg på - og skaden de kan gjøre er ikke begrenset til ditt eget bord.

La oss anta at vi vil tillate administratorer å slette poster fra aktivitetsloggen vår. En vanlig feil jeg har sett er følgende:

 Hvis ! tom ($ _ GET ['action']) && 'delete-activity-log' == $ _GET ['handling'] && isset ($ _ GET ['log_id'])) global $ wpdb; unsafe_delete_log ($ _ GET [ 'log_id']);  fungere unsafe_delete_log ($ log_id) global $ wpdb; $ sql = "SLETT FRA $ wpdb-> wptuts_activity_log WHERE log_id = $ log_id"; $ deleted = $ wpdb-> spørring ($ sql); 

Så hva er galt her? Rikelig: De har ikke merket tillatelser, så alle kan slette en aktivitetslogg. De har heller ikke sjekket nonces, så selv med tillatelse sjekker, kan en admin bruker bli lurt til å slette en logg. Dette var alt dekket i denne opplæringen. Men deres tredje feil forbinder de to første: unsafe_delete_log () funksjonen bruker den bestått verdien i en SQL-kommando uten å rømme den først. Dette lar det være åpent for manipulasjon.

La oss anta at den tilsiktede bruken er

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

Hva om en angriper besøkte (eller lurte en administrator til å besøke): www.unsafe-site.com?action=delete-activity-log&log_id=1;%20DROP%20TABLE%20wp_posts. De log_id inneholder en SQL-kommando, som deretter injiseres i $ sql og ville bli henrettet som:

 SLETT fra wp_wptuts_activity_log WHERE log_id = 1; DROP TABLE wp_posts

Resultatet: hele wp_posts bordet er slettet. Jeg har sett kode som dette på fora - og resultatet er at alle som besøker nettstedet, kan oppdatere eller slette noen bord i deres database.

Hvis de to første feilene ble korrigert, gjør det det vanskeligere for denne typen angrep på jobb - men ikke umulig, og det ville ikke beskytte mot en "angriper" som har tillatelse til å slette aktivitetslogger. Det er utrolig viktig å beskytte nettstedet mot SQL-injeksjoner. Det er også utrolig enkelt: WordPress gir forberede metode. I dette spesielle eksempelet:

 funksjonen safe_delete_log ($ log_id) global $ wpdb; $ sql = $ wpdb-> forberede ("DELETE fra $ wpdb-> wptuts_activity_log WHERE log_id =% d", $ log_id); $ deleted = $ wpdb-> spørring ($ sql)

SQL-kommandoen skulle nå utføres som

 SLETT fra wp_wptuts_activity_log WHERE log_id = 1;

Sanitizing Database Queries

Mest sanitering kan utføres utelukkende ved hjelp av $ wpdb global - spesielt gjennom sin forberede metode. Det gir også metoder for å sette inn og oppdatere data i tabeller på en sikker måte. Disse fungerer vanligvis ved å erstatte en ukjent inngang, eller assosiere en inngang, med et format plassholder. Dette formatet forteller WordPress hvilke data den kan forvente:

  • % s betyr en streng
  • % d betyr et heltall
  • % f betegner en flyte

Vi starter med å se på tre metoder som ikke bare renser spørsmål - men bygger dem for deg også.

Sette inn data

WordPress gir metoden $ Wpdb-> Sett (). Det er en wrapper for å sette inn data i databasen og håndterer sanitering. Det tar tre parametere:

  • Tabellnavn - navnet på bordet
  • Data - rekke data som skal settes inn som kolonne-> verdipar
  • formater - utvalg av formater for den tilsvarende verdien i datarammen (f.eks. % s, % d,% f)

Merk at nøklene til dataene skal være kolonner: hvis det finnes en nøkkel som ikke samsvarer med en kolonne, kan det hende at en feil blir kastet.

I eksemplene som følger, har vi eksplisitt satt dataene - men selvfølgelig generelt ville disse dataene ha kommet fra brukerinngang - slik at det kan være noe. Som diskutert i denne artikkelen er dataene bør har blitt validert først, for å returnere feil til brukeren - men vi trenger fortsatt å sanitisere dataene før du legger det til bordet vårt. Vi ser på validering i neste artikkel i denne serien.

 global $ wpdb; // $ user_id = 1; $ aktivitet = 1; $ object_id = 1479; $ activity_date = date_i18n ('Y-m-d H: i: s', falsk, sant); $ inserted = $ wpdb-> insert ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'aktivitet' => $ aktivitet, 'object_id' => $ object_id, 'activity_date' => $ activity_date,) , array ('% d', '% s', '% d', '% s',))); hvis ($ satt inn) $ insert_id = $ wpdb-> insert_id;  ellers // Insert failed

Oppdaterer data

For oppdatering av data i databasen vi har $ Wpdb-> oppdateringen (). Denne metoden aksepterer fem argumenter:

  • Tabellnavn - navnet på bordet
  • Data - rekke data som skal oppdateres som kolonne-> verdipar
  • Hvor - rekke data som skal matche som kolonne-> verdipar
  • Dataformat - rekke formater for de tilsvarende "data" -verdiene
  • Hvor Format - rekke formater for de tilsvarende "hvor" -verdiene

Dette oppdaterer alle rader som samsvarer med hvor arrayet med verdier fra dataregmentet. Igjen, som med $ Wpdb-> Sett () nøklene til datarammen må samsvare med en kolonne. Det kommer tilbake falsk På feil, eller antall rader oppdatert.

I følgende eksempel oppdaterer vi eventuelle poster med logg ID '14' (som burde være høyst en post, da dette er vår primære nøkkel). Den oppdaterer bruker-ID til 2 og aktivitet til 'redigert'.

 global $ wpdb; $ User_id = 2; $ Aktivitet = 'endrede; $ log_id = 14; $ updated = $ wpdb-> update ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'aktivitet' => $ aktivitet,), array ('log_id' => $ log_id,), array % d ','% s ​​'), array ('% d '),); hvis ($ oppdatert) // Antall rader oppdatert = $ oppdatert

slette

Siden 3.4 har WordPress også gitt $ Wpdb-> slett () Metode for å enkelt (og sikkert) slette rad (er). Denne metoden tar tre parametere:

  • Tabellnavn - navnet på bordet
  • Hvor - rekke data som skal matche som kolonne-> verdipar
  • formater - utvalg av formater for den tilsvarende verdi typen (f.eks. % s, % d,% f)

Hvis du vil at koden skal være kompatibel med WordPress pre-3.4, må du bruke $ Wpdb-> forberede metode for å desinfisere den riktige SQL-setningen. Et eksempel på dette ble gitt ovenfor. De $ Wpdb-> slett Metoden returnerer antall rader slettet, eller falsk på annen måte - slik at du kan avgjøre om slettingen var vellykket.

 global $ wpdb; $ deleted = $ wpdb-> delete ($ wpdb-> wptuts_activity_log, array ('log_id' => 14,), array ('% d');); hvis ($ slettet) // Antall rader slettet = $ slettet

esc_sql

I lys av ovennevnte metoder, og mer generelt $ Wpdb-> forberede () metode diskutert neste, denne funksjonen er litt overflødig. Den er gitt som et nyttig omslag for $ Wpdb-> escape () Metode, selv en herliggjort addslashes. Siden det vanligvis er mer hensiktsmessig og tilrådelig å bruke de ovennevnte tre metodene, eller $ Wpdb-> forberede (), du vil sikkert finne ut at du sjelden trenger å bruke esc_sql ().

Som et enkelt eksempel:

 $ activity = 'kommentert'; $ sql = "SLETT FRA $ wpdb-> wptuts_activity_log WHERE.esc_sql ($ aktivitet)." ";";

Generelle spørsmål

For generelle SQL-kommandoer der (dvs. de som ikke setter inn, fjerner eller oppdaterer rader) må vi bruke metoden $ Wpdb-> forberede (). Den aksepterer et variabelt antall argumenter. Den første er SQL-spørringen vi ønsker å utføre med alle "ukjente" data erstattet av deres passende formatholder. Disse verdiene sendes som tilleggsargumenter, i den rekkefølgen de vises.

For eksempel i stedet for:

 $ sql = "SELECT * FRA $ wpdb-> wptuts_activity_log WHERE user_id = $ user_id OG object_id = $ object_id OG aktivitet = $ aktivitet ORDER BY activity_date $ order"; $ logs = $ wpdb-> get_results ($ sql);

vi har

 $ sql = $ wpdb-> forberede ("VELG * FRA $ wpdb-> wptuts_activity_log HVOR user_id =% d OG object_id =% d OG aktivitet =% s BESTILL BY activity_date% s", $ user_id, $ object_id, $ aktivitet , $ rekkefølge); $ logs = $ wpdb-> get_results ($ sql);

De forberede Metoden gjør to ting.

  1. Det gjelder mysql_real_escape_string () (eller addslashes ()) til verdiene blir satt inn. Spesielt vil dette forhindre verdier som inneholder anførselstegn fra å hoppe ut av spørringen.
  2. Det gjelder vsprintf () når du legger til verdiene i spørringen for å sikre at de er formatert på riktig måte (så heltal er heltall, flyter flyter osv.). Dette er grunnen til at vårt eksempel i begynnelsen av artikkelen fjernet alt, men "1".

Mer kompliserte spørringer

Du burde finne det $ Wpdb-> forberede, sammen med innsatsen, oppdatering og sletting av metoder er alt du virkelig trenger. Noen ganger er det omstendigheter der man ønsker en mer manuell tilnærming - noen ganger bare fra lesbarhetssynspunkt. For eksempel, anta at vi har et ukjent utvalg av aktiviteter som vi ønsker alle loggene. Vi * kan * dynamisk legge til % s plassholderne til SQL-spørringen, men en mer direkte tilnærming virker lettere:

 // Et ukjent utvalg som skal inneholde strenger blir forespurt for $ activities = array (...); // Sanitize innholdet i array $ activities = array_map ('esc_sql', $ aktiviteter); $ activities = array_map ('sanitize_title_for_query', $ aktiviteter); // Opprett en streng fra den sanitiserte gruppen som danner den indre delen av IN (...) setningen $ in_sql = "'". implodere ("','", $ aktiviteter). "'"; // Legg dette til spørringen $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE aktivitet IN ($ in_sql);" // Utfør spørringen $ logs = $ wpdb-> get_results ($ sql);

Tanken er å søke esc_sql og sanitize_title_for_query til hvert element i gruppen. Den første legger til skråstreker for å unnslippe vilkårene - ligner på hva $ Wpdb-> forberede () gjør. Den andre gjelder bare sanitize_title_with_dashes () - selv om oppførselen kan endres fullstendig gjennom filtre. Den egentlige SQL-setningen dannes ved å imploding det nå sanitiserte arrayet i en kommaseparert streng, som legges til i IN (...) en del av spørringen.

Hvis arrayet forventes å inneholde heltall så er det nok å bruke intval () eller Absint () å desinfisere hvert element i matrisen.

hvitlisting

I andre tilfeller kan hvitlisting være hensiktsmessig. For eksempel kan det ukjente innspillet være en rekke kolonner som skal returneres i spørringen. Siden vi vet hva kolonnene i databasen er, kan vi bare hviteliste dem - fjerne felt som vi ikke gjenkjenner. Men for å gjøre vår kode menneskelig vennlig, bør vi være uskadelig. For å gjøre dette konverterer vi alt vi mottar til små bokstaver - siden i del ett brukte vi spesifikt små kolonneavn.

 // Et ukjent utvalg som skal inneholde kolonner som skal inkluderes i spørringen $ felt = array (...); // En hviteliste med tillatte felt $ allowed_fields = array (...); // Konverter felt til små bokstaver (som våre kolonneavn er alle små bokstaver - se del 1) $ felt = array_map ('strtolower', $ felt); // Sanitize ved hvit notering $ felt = array_intersect ($ felt, $ allowed_fields); // Returner bare markerte felt. Tom $ felt er tolket som alle om (tomt ($ felt)) $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log";  else $ sql = "SELECT" .implode (',', $ felt). "FRA $ wpdb-> wptuts_activity_log";  // Utfør spørringen $ logs = $ wpdb-> get_results ($ sql);

Whitelisting er også praktisk når du setter inn REKKEFØLGE ETTER del av spørringen (hvis dette er angitt av brukerinngang): data kan bestilles som DESC eller ASC bare.

 // Ukjent brukerinngang (forventes å være asc eller desc) $ order = $ _GET ['order']; // Tillat inngang til å være noen, eller blandet, sak $ order = strtoupper ($ rekkefølge); // Sanitert ordreverdi $ order = ('ASC' == $ rekkefølge? 'ASC': 'DEC');

LIKE spørsmål

SQL LIKE uttalelser støtter bruken av jokertegn som % (null eller flere tegn) og _ (nøyaktig ett tegn) når du matcher verdier til spørringen. For eksempel verdien foobar ville matche noen av spørringene:

 SELECT * FROM $ wpdb-> wptuts_activity_log HVOR aktivitet LIKE 'foo%' VELG * FRA $ wpdb-> wptuts_activity_log HVOR aktivitet LIKE '% bar' VELG * FRA $ wpdb-> wptuts_activity_log HVOR aktivitet LIKE '% oba%' VELG * FRA $ wpdb-> wptuts_activity_log HVOR aktivitet som 'fo_bar%'

Disse spesialtegnene kan imidlertid faktisk være tilstede i termen som søkte etter - og for å forhindre at de tolkes som jokertegn, må vi unnslippe dem. For denne WordPress gir like_escape () funksjon. Merk at dette ikke hindrer SQL-injeksjon - men bare unngår % og _ tegn: du trenger fortsatt å bruke esc_sql () eller $ Wpdb-> forberede ().

 // Samle term $ term = $ _GET ['aktivitet']; // Unngå noen jokertegn $ term = like_escape ($ term); $ sql = $ wpdb-> forbered ("VELG * FRA $ wpdb-> wptuts_activity_log HVOR aktivitet LIKE% s", '%'. $ term. '%'); $ logs = $ wpdb-> get_results ($ sql);

Query Wrapper Funksjoner

I eksemplene vi har sett har vi brukt to andre metoder for $ wpdb:

  • $ wpdb-> spørring ($ sql) - Dette utfører en hvilken som helst forespørsel gitt til den og returnerer antall berørte rader.
  • $ wpdb-> get_results ($ sql, $ ouput) - Dette utfører spørringen gitt til den og returnerer det tilsvarende resultatsettet (dvs. de matchende radene). $ utgang angir formatet for de returnerte resultatene:
    • ARRAY_A - numerisk rekke av rader, hvor hver rad er en assosiativ array, tastet av kolonnene.
    • ARRAY_N - numerisk rekke av rader, hvor hver rad er en numerisk rekkefølge.
    • GJENSTAND - numerisk rekke av rader, hvor hver rad er en radobjekt. Misligholde.
    • OBJECT_K - assosiativ rekke av rader (tastet av verdien av den første kolonnen), hvor hver rad er en assosiativ array.

Det er andre vi ikke har nevnt også:

  • $ wpdb-> get_row ($ sql, $ ouput, $ rad) - Dette utfører spørringen og returnerer en rad. $ rad setter hvilken rad som skal returneres, som standard er dette 0, den første matchende raden. $ utgang setter formatet på raden:
    • ARRAY_A - Row er en kolonne => verdi par.
    • ARRAY_N - Row er numerisk rekkeverdier.
    • GJENSTAND - Row returneres som et objekt. Misligholde.
  • $ wpdb-> get_col ($ sql, $ kolonne) - Dette utfører spørringen og returnerer et numerisk utvalg av verdier fra den angitte kolonnen. $ kolonne Angir hvilken kolonne som skal returneres som heltall. Som standard er dette 0, den første kolonnen.
  • $ wpdb-> get_var ($ sql, $ kolonne, $ rad) - Dette utfører spørringen og returnerer en bestemt verdi. $ rad og $ kolonne er som ovenfor, og angi hvilken verdi som skal returneres. For eksempel,
     $ activities_by_user_1 = $ wpdb-> get_var ("VELG COUNT (*) FRA $ wpdb-> wptuts_activity_log WHERE user_id = 1");

Det er viktig å merke seg at disse metodene bare er wrappers for å utføre en SQL-spørring og formatere resultatet. De saniterer ikke spørringen - så du bør ikke bruke dem alene når spørringen inneholder noen "ukjente" data.


Sammendrag

Vi har dekket ganske mye i denne opplæringen - og data sanitering er et viktig tema å forstå. I den neste artikkelen bruker vi den til plugin-modulen vår. Vi ser på å utvikle et sett med innpakningsfunksjoner (ligner på funksjoner som wp_insert_post (), wp_delete_post () etc.) som vil legge til et lag av abstraksjon mellom plugin-modulen og databasen.