Aangepaste databasetabellen veiligheid eerst

Dit is deel twee van een serie over aangepaste databasetabellen in WordPress. In deel één hebben we de redenen voor en tegen het gebruik van aangepaste tabellen besproken. We hebben gekeken naar enkele details die in overweging moeten worden genomen - kolomnaamgeving, kolomtypen - en ook hoe de tabel moet worden gemaakt. Voordat we verder gaan, moeten we bespreken hoe we met deze nieuwe tabel kunnen omgaan veilig. In een vorig artikel behandelde ik algemene sanitisation en validatie - in deze tutorial zullen we dit in meer detail bekijken in de context van databases.

Veiligheid bij het werken met een databasetabel staat voorop - daarom behandelen we het al vroeg in de serie. Als het niet juist wordt gedaan, kunt u uw tabel open laten staan ​​voor manipulatie via SQL-injectie. Het kan een hacker toelaten informatie te extraheren, inhoud te vervangen of zelfs de manier waarop uw site zich gedraagt ​​te wijzigen - en de schade die ze kunnen toebrengen is niet beperkt tot uw aangepaste tabel.

Laten we veronderstellen dat we beheerders toestemming willen geven om records uit ons activiteitenlogboek te verwijderen. Een veelgemaakte fout die ik heb gezien, is de volgende:

 if (! empty ($ _ GET ['action']) && 'delete-activity-log' == $ _GET ['action'] && isset ($ _ GET ['log_id'])) global $ 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); 

Dus wat is er mis hier? Veel: ze hebben geen rechten gecontroleerd, dus iedereen kan een activiteitenlog verwijderen. Ze hebben ook geen nonces gecontroleerd, dus zelfs met toestemmingcontroles kan een admin-gebruiker misleid worden om een ​​logboek te verwijderen. Dit werd allemaal behandeld in deze tutorial. Maar hun derde fout verbindt de eerste twee: de unsafe_delete_log () functie gebruikt de gepasseerde waarde in een SQL-opdracht zonder eerst te ontsnappen. Dit laat het wijd open voor manipulatie.

Laten we aannemen dat het bedoelde gebruik ervan is

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

Wat als een aanvaller een bezoek heeft gebracht (of een beheerder heeft misleid tot een bezoek): www.unsafe-site.com?action=delete-activity-log&log_id=1;%20DROP%20TABLE%20wp_posts. De log_id bevat een SQL-opdracht, die vervolgens wordt geïnjecteerd $ sql en zou worden uitgevoerd als:

 DELETE van wp_wptuts_activity_log WHERE log_id = 1; DROP TABLE wp_posts

Het resultaat: het geheel wp_posts tabel is verwijderd. Ik heb code zoals deze op forums gezien - en het resultaat is dat iedereen die zijn site bezoekt kan updaten of verwijderen ieder tabel in hun database.

Als de eerste twee fouten zijn gecorrigeerd, maakt dit het moeilijker voor dit type aanval om te werken - maar niet onmogelijk en zou het niet beschermen tegen een 'aanvaller' die toestemming heeft om activiteitenlogboeken te verwijderen. Het is ongelooflijk belangrijk om uw site te beschermen tegen SQL-injecties. Het is ook ongelooflijk eenvoudig: WordPress biedt het bereiden methode. In dit specifieke voorbeeld:

 function safe_delete_log ($ log_id) global $ wpdb; $ sql = $ wpdb-> prepare ("DELETE from $ wpdb-> wptuts_activity_log WHERE log_id =% d", $ log_id); $ deleted = $ wpdb-> query ($ sql)

De SQL-opdracht wordt nu uitgevoerd als

 DELETE van wp_wptuts_activity_log WHERE log_id = 1;

Databasequery's ontsmetten

De meeste sanitisaties kunnen uitsluitend worden uitgevoerd met behulp van de $ wpdb wereldwijd - met name via zijn bereiden methode. Het biedt ook methoden voor het veilig invoegen en bijwerken van gegevens in tabellen. Deze werken meestal door een onbekende invoer te vervangen of een invoer te associëren met een tijdelijke plaatsaanduiding. Dit formaat vertelt WordPress welke gegevens het kan verwachten:

  • % s duidt een string aan
  • % d geeft een geheel getal aan
  • % f geeft een float aan

We beginnen met het bekijken van drie methoden die niet alleen zoekopdrachten zuiveren, maar deze ook voor u bouwen.

Gegevens invoegen

WordPress biedt de methode $ Wpdb-> invoegen (). Het is een verpakking voor het invoegen van gegevens in de database en handelt de reiniging af. Het heeft drie parameters nodig:

  • Tafel naam - de naam van de tafel
  • Gegevens - reeks gegevens die moet worden ingevoegd als kolom-> waardeparen
  • formats - reeks indelingen voor de overeenkomstige waarde in de gegevensmatrix (bijv. % s, % d,% f)

Merk op dat de sleutels van de gegevens kolommen moeten zijn: als er een sleutel is die niet overeenkomt met een kolom, kan er een fout worden gegenereerd.

In de voorbeelden die volgen, hebben we de gegevens expliciet vastgelegd, maar in het algemeen zouden deze gegevens afkomstig zijn van gebruikersinvoer - dus het kan van alles zijn. Zoals besproken in dit artikel de gegevens moeten zijn eerst gevalideerd om eventuele fouten aan de gebruiker te retourneren, maar we moeten de gegevens nog steeds opschonen voordat deze aan onze tabel wordt toegevoegd. We zullen validatie bekijken in het volgende artikel van deze serie.

 globale $ wpdb; // $ user_id = 1; $ activiteit = 1; $ object_id = 1479; $ activity_date = date_i18n ('Y-m-d H: i: s', false, true); $ placed = $ 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',)); if ($ ingevoegd) $ insert_id = $ wpdb-> insert_id;  else // invoegen mislukt

Gegevens bijwerken

Voor het updaten van gegevens in de database die we hebben $ Wpdb-> update (). Deze methode accepteert vijf argumenten:

  • Tafel naam - de naam van de tafel
  • Gegevens - reeks gegevens die moet worden bijgewerkt als kolom-> waardeparen
  • Waar - reeks gegevens die moeten worden vergeleken als kolom-> waardeparen
  • Data formaat - reeks indelingen voor de bijbehorende 'gegevens'-waarden
  • Waar formaat - matrix van indelingen voor de overeenkomstige 'waar'-waarden

Hiermee worden rijen die overeenkomen met de array where met waarden uit de gegevensmatrix bijgewerkt. Nogmaals, zoals met $ Wpdb-> invoegen () de sleutels van de gegevensmatrix moeten overeenkomen met een kolom. Het komt terug vals op fout of het aantal rijen dat is bijgewerkt.

In het volgende voorbeeld werken we alle records bij met log-ID '14' (wat maximaal één record zou moeten zijn, omdat dit onze primaire sleutel is). Het werkt de gebruikers-ID bij naar 2 en de activiteit naar 'bewerkt'.

 globale $ wpdb; $ User_id = 2; $ Activiteit = 'bewerkt'; $ 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 '),); if ($ updated) // Aantal rijen bijgewerkt = $ bijgewerkt

wissen

Sinds 3.4 heeft WordPress ook de $ Wpdb-> delete () methode voor het eenvoudig (en veilig) verwijderen van rij (len). Deze methode heeft drie parameters:

  • Tafel naam - de naam van de tafel
  • Waar - reeks gegevens die moeten worden vergeleken als kolom-> waardeparen
  • formats - reeks indelingen voor het overeenkomstige waardetype (bijv. % s, % d,% f)

Als u wilt dat uw code compatibel is met WordPress pre-3.4, moet u de code gebruiken $ Wpdb-> bereiden methode om de juiste SQL-instructie te ontsmetten. Een voorbeeld hiervan is hierboven gegeven. De $ Wpdb-> delete methode retourneert het aantal verwijderde rijen of false, zodat u kunt bepalen of de verwijdering is gelukt.

 globale $ wpdb; $ deleted = $ wpdb-> delete ($ wpdb-> wptuts_activity_log, array ('log_id' => 14,), array ('% d'),); if ($ deleted) // Aantal rijen verwijderd = $ verwijderd

esc_sql

In het licht van de bovenstaande methoden, en de meer algemene $ Wpdb-> bereiden () methode die hierna wordt besproken, is deze functie een beetje overbodig. Het wordt aangeboden als een handige verpakking voor de $ Wpdb-> escape () methode, zelf een veredeld addslashes. Aangezien het meestal meer geschikt en raadzaam is om de bovenstaande drie methoden te gebruiken, of $ Wpdb-> bereiden (), u zult waarschijnlijk vinden dat u zelden moet gebruiken esc_sql ().

Als een eenvoudig voorbeeld:

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

Algemene vragen

Voor algemene SQL-opdrachten waar (dat wil zeggen degene die geen rijen invoegen, verwijderen of bijwerken) moeten we de methode gebruiken $ Wpdb-> bereiden (). Het accepteert een variabel aantal argumenten. De eerste is de SQL-query die we willen uitvoeren met alle 'onbekende' gegevens vervangen door hun geschikte tijdelijke plaatsaanduiding. Deze waarden worden doorgegeven als extra argumenten, in de volgorde waarin ze worden weergegeven.

Bijvoorbeeld in plaats van:

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

wij hebben

 $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id =% d AND object_id =% d AND activity =% s BESTELLING BY activity_date% s", $ user_id, $ object_id, $ activiteit $ bestelling); $ logs = $ wpdb-> get_results ($ sql);

De bereiden methode doet twee dingen.

  1. Het is van toepassing mysql_real_escape_string () (of addslashes ()) naar de waarden die worden ingevoegd. Dit voorkomt met name dat waarden met aanhalingstekens uit de query springen.
  2. Het is van toepassing vsprintf () bij het toevoegen van de waarden aan de query om ervoor te zorgen dat ze op de juiste manier worden opgemaakt (dus gehele getallen zijn gehele getallen, drijvers zijn drijvers, enzovoort). Dit is de reden waarom ons voorbeeld helemaal aan het begin van het artikel alles behalve de '1'.

Meer gecompliceerde zoekopdrachten

Dat zou je moeten vinden $ Wpdb-> bereiden, samen met de invoeg-, update- en verwijdermethoden is alles wat je echt nodig hebt. Soms zijn er omstandigheden waarbij een meer 'handmatige' benadering gewenst is - soms alleen vanuit het oogpunt van leesbaarheid. Stel dat we een onbekende reeks activiteiten hebben waarvoor we alle logboeken willen hebben. We * kunnen * de * dynamisch toevoegen % s tijdelijke aanduidingen voor de SQL-query, maar een directere benadering lijkt eenvoudiger:

 // Een onbekende array die strings moet bevatten die worden opgevraagd voor $ activities = array (...); // Sanitize de inhoud van de array $ activities = array_map ('esc_sql', $ activiteiten); $ activities = array_map ('sanitize_title_for_query', $ activiteiten); // Maak een string uit de ge-sanitiseerde array die het binnenste gedeelte van de IN (...) -instructie $ in_sql = "'" vormt. imploderen ("','", $ activiteiten). """; // Voeg dit toe aan de zoekopdracht $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activity IN ($ in_sql);" // Voer de query $ logs = $ wpdb-> get_results ($ sql) uit;

Het idee is om toe te passen esc_sql en sanitize_title_for_query voor elk element in de array. De eerste voegt slashes toe om aan de voorwaarden te ontsnappen - vergelijkbaar met wat $ Wpdb-> bereiden () doet. De tweede is eenvoudig van toepassing sanitize_title_with_dashes () - hoewel het gedrag volledig kan worden aangepast via filters. De eigenlijke SQL-instructie wordt gevormd door de nu gesanitiseerde array te imploderen in een door komma's gescheiden tekenreeks, die wordt toegevoegd aan de IN (...) deel van de vraag.

Als wordt verwacht dat de array gehele getallen bevat, is het voldoende om te gebruiken intval () of absint () om elk element in de array te ontsmetten.

whitelisting

In andere gevallen is het toegestaan ​​om op de witte lijst te plaatsen. De onbekende invoer kan bijvoorbeeld een reeks kolommen zijn die in de query moeten worden geretourneerd. Omdat we weten wat de kolommen van de database zijn, kunnen we ze gewoon op de witte lijst zetten - velden verwijderen die we niet herkennen. Om onze code echter mensvriendelijk te maken, moeten we niet hoofdlettergevoelig zijn. Om dit te doen, zullen we alles wat we ontvangen naar kleine letters converteren - omdat we in deel één specifiek kolomletternamen in kleine letters hebben gebruikt.

 // Een onbekende array die kolommen moet bevatten die moeten worden opgenomen in de query $ fields = array (...); // Een witte lijst met toegestane velden $ allowed_fields = array (...); // Converteer velden naar kleine letters (onze kolomnamen zijn allemaal kleine letters - zie deel 1) $ fields = array_map ('strtolower', $ fields); // Sanitize by white list $ fields = array_intersect ($ fields, $ allowed_fields); // Alleen geselecteerde velden retourneren. Lege $ velden worden geïnterpreteerd als alle if (leeg ($ velden)) $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log";  else $ sql = "SELECT" .implode (',', $ velden). "FROM $ wpdb-> wptuts_activity_log";  // Voer de query $ logs = $ wpdb-> get_results ($ sql) uit;

Whitelisting is ook handig bij het instellen van de BESTELLING DOOR deel van de query (als dit is ingesteld door gebruikersinvoer): gegevens kunnen als worden gerangschikt DESC of ASC enkel en alleen.

 // Onbekende gebruikersinvoer (naar verwachting asc of desc) $ order = $ _GET ['order']; // Sta invoer toe om een ​​willekeurige, of gemengde, case $ order = strtoupper ($ order) te zijn; // Geautomatiseerde orderwaarde $ order = ('ASC' == $ order? 'ASC': 'DEC');

LIKE Queries

SQL LIKE-statements ondersteunen het gebruik van wildcards zoals % (nul of meer tekens) en _ (exact één teken) bij het afstemmen van waarden op de query. Bijvoorbeeld de waarde foobar zou overeenkomen met een van de vragen:

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

Deze speciale tekens kunnen echter wel aanwezig zijn in de term waarnaar wordt gezocht - en om te voorkomen dat ze worden geïnterpreteerd als joker - moeten we eraan ontsnappen. Hiervoor biedt WordPress de like_escape () functie. Merk op dat dit de SQL-injectie niet voorkomt, maar alleen de % en _ tekens: je moet nog steeds gebruiken esc_sql () of $ Wpdb-> bereiden ().

 // Verzamel term $ term = $ _GET ['activiteit']; // Ontsnappen aan elke wildcard $ term = like_escape ($ term); $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activity LIKE% s", '%'. $ term. '%'); $ logs = $ wpdb-> get_results ($ sql);

Query Wrapper Functies

In de voorbeelden die we hebben bekeken hebben we twee andere methoden gebruikt $ wpdb:

  • $ wpdb-> query ($ sql) - Hiermee voert u een query uit die wordt gegeven en wordt het aantal betrokken rijen geretourneerd.
  • $ wpdb-> get_results ($ sql, $ ouput) - Dit voert de query uit die wordt gegeven en retourneert de overeenkomende resultatenset (dat wil zeggen de overeenkomende rijen). $ uitgang bepaalt het formaat van de geretourneerde resultaten:
    • ARRAY_A - numerieke array van rijen, waarbij elke rij een associatieve array is, ingetoetst door de kolommen.
    • ARRAY_N - numerieke array van rijen, waarbij elke rij een numerieke array is.
    • VOORWERP - numerieke reeks rijen, waarbij elke rij een rijobject is. Standaard.
    • OBJECT_K - associatieve array van rijen (ingetoetst door de waarde van de eerste kolom), waarbij elke rij een associatieve array is.

Er zijn nog andere die we niet hebben genoemd:

  • $ wpdb-> get_row ($ sql, $ ouput, $ row) - Hiermee voert u de query uit en wordt één rij geretourneerd. $ row stelt in welke rij moet worden geretourneerd, standaard is dit 0, de eerste overeenkomende rij. $ uitgang bepaalt het formaat van de rij:
    • ARRAY_A - Rij is een kolom => value paar-.
    • ARRAY_N - Rij is een numerieke reeks waarden.
    • VOORWERP - Rij wordt geretourneerd als een object. Standaard.
  • $ wpdb-> get_col ($ sql, $ column) - Hiermee wordt de query uitgevoerd en wordt een numerieke waardenarray geretourneerd uit de opgegeven kolom. $ kolom geeft aan welke kolom als integer moet worden geretourneerd. Standaard is dit 0, de eerste kolom.
  • $ wpdb-> get_var ($ sql, $ column, $ row) - Hiermee wordt de query uitgevoerd en wordt een bepaalde waarde geretourneerd. $ row en $ kolom zijn zoals hierboven en specificeren welke waarde moet worden geretourneerd. Bijvoorbeeld,
     $ activities_by_user_1 = $ wpdb-> get_var ("SELECT COUNT (*) VAN $ wpdb-> wptuts_activity_log WHERE user_id = 1");

Het is belangrijk om op te merken dat deze methoden gewoon wrappers zijn voor het uitvoeren van een SQL-query en het formatteren van het resultaat. Ze zuiveren de zoekopdracht niet - dus u zou ze niet alleen moeten gebruiken wanneer de query een aantal 'onbekende' gegevens bevat.


Samenvatting

We hebben nogal wat behandeld in deze tutorial - en data-sanitisatie is een belangrijk te begrijpen onderwerp. In het volgende artikel zullen we het toepassen op onze plug-in. We zullen kijken naar het ontwikkelen van een set wrapper-functies (vergelijkbaar met functies zoals wp_insert_post (), wp_delete_post () enz.) die een abstractielaag zal toevoegen tussen onze plug-in en de database.