Leer alles over SQL

SQL injections

| geschreven door Andy

Als je werkt met queries en gegevens dan zijn er mogelijk kwaadwillenden die kwetsbaarheden in je queries proberen te vinden. En daardoor zijn ze in staat om de queries te manipuleren, ook wel bekend als SQL Injection.

Wat is een SQL Injection?

Een SQL injection is een manipulatie van de query, waardoor het uiteindelijke resultaat anders is dan de bedoeling zou moeten zijn. Dit manipuleren kan bestaan uit het opvragen van gegevens die normaal niet voor de gebruiker zichtbaar zou moeten zijn. Daarnaast is het mogelijk om gegevens te wijzigen of te verwijderen, terwijl dit niet zou moeten.

Het kan nog gevaarlijker: het is zelfs mogelijk om commando’s uit te voeren, waarmee tabellen of database worden verwijderd of waarmee zelfs de database service wordt beëindigd. Er zijn dus verschillende soorten SQL injecties te onderscheiden: van vrij onschuldig tot vrij catastrofaal.

Is een SQL injection te voorkomen?

Het is mogelijk om door het zetten van enkele kleine stappen de kans op een SQL injection te voorkomen/ Valideer altijd ingevoerde data. Vertrouw de gebruiker nooit blindelings.

Zorg er altijd voor dat je data ‘escaped’ als het invoert, zodat er niets stuk kan gaan en verdiep je vooral in prepared statements. Hiermee leg je de structuur van een query vooraf al vast.

Soorten SQL Injections

Er zijn een aantal soorten SQL Injections:

  • Comments
  • Incorrectly filtered escape characters
  • Incorrectly type handling
  • Blind injection
  • Union injections

Comments

Net als bij veel andere programmeertalen is het ook bij SQL mogelijk om commentaar in de query mee te geven. Dit commentaar wordt niet uitgevoerd en dient meestal slechts ter verduidelijking van de query. Echter is het mogelijk om een zogeheten line comment te gebruiken als manipulatie van een parameter. Op deze manier kan ervoor worden gezorgd dat een deel van de query wordt genegeerd. Een line comment wordt aangegeven met '--.

We nemen het volgende stukje PHP code dat gebruikt maakt van geposte waardes uit een formulier.

$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM members WHERE username = '{$username}' AND password = '{$password}'"

Door het simpelweg posten van admin'-- als gebruikersnaam kan ervoor gezorgd worden dat het tweede deel van de query genegeerd wordt. Het belangrijkste deel van een inlogsysteem wordt dus buiten spel gezet: het wachtwoord. De gebruiker kan dus inloggen als een ‘admin’, zonder hiervoor een wachtwoord te hoeven weten.

SELECT * FROM members WHERE username = 'admin'--' AND password = '{$password}'

Incorrectly filtered escape characters

Deze SQL-injectie bestaat uit het manipuleren van een query om zo acties met de database te kunnen uitvoeren die normaal niet toegestaan zijn. We nemen het volgende voorbeeld.

$username = $_POST['username'];
$query = "SELECT * FROM users WHERE name = '{$username}'";

Deze query kunnen we manipuleren door de waaarde a' or 't' ='t als gebruikersnaam te posten. Het volgende voegen we toe.

SELECT * FROM users WHERE name = 'a' OR 't'='t';

Het raden van een naam is in dit geval niet meer nodig, want de OR met de vergelijking 't' = 't' slaagt sowieso en op deze manier is de query dus omzeild. In dit voorbeeld hebben we de naam omzeild, maar dit kan dus ook goed werken voor het omzeilen van een wachtwoord.

Maar als we op deze manier een vrij simpel OR statement kunnen toevoegen, is het dan ook mogelijk om een hele tabel te verwijderen? In principe wel: a'; DROP TABLE users;--

Hierdoor zal de tabel voor gebruikers verwijderd worden. De query ziet er dan als volgt uit.

SELECT * FROM users WHERE name = 'a';DROP TABLE users--';

Incorrect type handling

Bij dit type SQL injectie wordt er niet gecontroleerd of de ingegeven waarde wel van het juiste type is. Als er bijvoorbeeld een numerieke waarde wordt verwacht, dan is het verstandig om te controleren of de ingegeven waarde ook daadwerkelijk numeriek is. Als dit wordt nagelaten dan kunnen kwaadwillende de query zodanig manipuleren dat ongewenste gegevens in de database worden aangepast of worden verwijderd. Genomen het volgende voorbeeld.

$id = $_GET['id'];
$query = "SELECT * FROM data WHERE id = {$id}"

We verwachten dat $id een numerieke waarde bevat, maar dit controleren we nergens. We zouden dus in principe 1; DROP TABLE users kunnen opgeven als id. Omdat er nergens een goede controle is zal de applicatie gewoon verder uitgevoerd worden, maar de tabel users is dan wel verdwenen.

SELECT * FROM DATA WHERE id=1;DROP TABLE users;

Blind injection

Een blind sql-injection is identiek aan een gewonen sql-injection echter wordt er op deze manier geprobeerd om bruikbare foutmelding te achterhalen. Er worden een aantal true/false statements meegegeven in de injectie waarmee een foutmelding gegenereerd wordt. voorbeeld:

SELECT naam FROM TEST1 WHERE naam = 'Evert' AND 1=1;

In de code hierboven wordt het SQL statement correct uitgevoerd en zal de data gewoon in de applicatie te zien zijn. Terwijl in de code hieronder geen resultaat geneerd. Er zullen een aantal foutmelding op het scherm weergegeven worden.

SELECT naam FROM TEST1 WHERE naam = 'Evert' AND 1=2;

Union Injections

Dit deel over UNION Injections wordt herzien. Wijzigingen worden verwacht in de eerste helft van 2018.

Door middel van UNION is het mogelijk om de resultaten van twee tabellen samen te voegen. Het is noodzakelijk dat in beide queries hetzelfde aantal kolommen worden geselecteerd. De reden hiervoor is dat de resultaten uit de tweede tabel onder de kolomnamen van de eerste tabel komen te staan.

SELECT id, titel, naam FROM tabel1 UNION ALL SELECT id, tekst, opmerking FROM tabel2;

De velden id, tekst en opmerking uit tabel 2 worden in het resultaat geplaatst onder de velden id, titel en naam. De typen uit beide tabellen moeten overeen zijn. Als het veld ID een integer is dan dient het eerste veld uit de tweede tabel ook van dat type te zijn.

Het risico van SQL injections is de UNION injection. Deze injection is eenvoudig te gebruiken vanuit de adresbalk. Door middel van UNION te gebruiken kunnen er gegevens opgehaald die niet voor de gebruiker bestemd zijn.

In onderstaand voorbeeld wordt aan de hand van de URL een UNION injection uitgevoerd:

?id=12349999' UNION ALL SELECT 1,2,3,4,5,6,7 FROM [tabelnaam]--

Wat gebeurd er in deze request: ?id=12349999′ Er wordt een niet bestaand id geraden, zodat er uit de eerste tabel geen data wordt getoond. Dit ID wordt afgesloten zodat UNION in dezelfde query komt.
UNION ALL SELECT Er wordt een nieuw verzoek naar een tabel ingevoegd.
FROM [tabelnaam] [tabelnaam] kan naar eigen inzicht worden gegokt.
— De rest van de eventuele query wordt uitgecommentarieerd.
De query die wordt uitgevoerd ziet er als volgt uit:

SELECT * FROM tabel UNION ALL SELECT 1,2,3,4,5,6,7 FROM [tabelnaam]

Echter is het nadelig dat de tabelnaam gegokt moet worden. Hiervoor is er de volgende mogelijkheid, waarmee het mogelijk is om tabelnamen te achterhalen

?id=12349999' UNION ALL SELECT 1,TABLE_NAME,3,TABLE_ROWS,5 FROM information_schema.tables  --

Deze query vraagt vraagt een overzicht op van alle tabellen uit de database information_schema. In deze database wordt de structuur van alle databases, tabellen en dergelijke opgeslagen. In feite is het weten van alle tabelnamen zinloos, want niet alles is direct te benaderen vanuit de applicatie. Om achter de daadwerkelijke database naam te komen kan het volgende gedaan worden:

?id=12349999' union all select 1,2,3,4,5,6,7 from tabelnaamdienietbestaat --

Als de foutweergave is ingeschakeld dan komt de volgende fout in beeld:

1146: Table 'databasenaam.tabelnaamdienietbestaat' doesn't exist

Nu de databasenaam bekend is wordt het mogelijk om daadwerkelijk tabelnamen op de vragen.

?id=12349999'
UNION ALL
    SELECT 1,TABLE_NAME,3,TABLE_ROWS,5,6,7
    FROM information_schema.tables
    WHERE TABLE_SCHEMA='databasenaam'
    LIMIT 1,1  --

De limit staat er in, omdat er altijd maar één naam gegeven zal worden. Deze kan in de url verhoogd en verlaagd worden om de juiste tabelnaam te hanteren. Als de tabelnaam bekend is, dan wordt het mogelijk om de kolommen op te vragen:

?id=12349999'
UNION ALL
    SELECT 1,COLUMN_NAME,3,PRIVILEGES,5,6,7
    FROM information_schema.columns
    WHERE TABLE_SCHEMA= 'databasenaam' AND TABLE_NAME='tabelnaam'
    LIMIT 1,1--

Als de kolommen bekend zijn dan kan hieruit de data worden getoond:

?id=12349999' union all select 1,user,3,password,5,6,7 from members LIMIT 1,1--

Op deze manier kan data worden verkregen die niet is bedoeld om door anderen gebruikt te worden. Waardoor het mogelijk wordt om bijvoorbeeld cookies over te nemen.

SQL Injections voorkomen

In principe moet je nooit inkomende data blindelings vertrouwen. Hoe betrouwbaar de gebruikersgroep er ook uit mag zien. Er zijn een aantal manieren om SQL injections te voorkomen.

Probeer de waardes altijd te casten.

Als we een veld nodig hebben dat alleen een numerieke waarde bevatten, dan kunnen we zorgen dat we deze waarde casten naar int zodat we zeker weten dat we een integer waarde binnen krijgen:

$id = (int) $_GET['id'];
$query = "SELECT * FROM data WHERE id = {$id}"

Escapen van strings

In PHP is het mogelijk om speciale karakters zoals ' te escapen. Hiervoor kan de functie mysqli_real_escape_string gebruikt worden.

$username = mysqli_real_escape_string( $_POST['username'] );
$query = "SELECT * FROM users WHERE name = '{$username}'";

Prepared statements

Door middel van een prepared statement wordt de structuur van de query vooraf al vastgelegd. We geven daarbij tevens aan welke waardes we op welke plekken verwachten. Alles wat daarvan afwijkt zal resulteren in een foutieve query.

Beperken van de gebruikersrechten

In principe is dit het verdoezelen van een mogelijk probleem. Maar als je echt wilt voorkomen dat er mogelijk tabellen worden verwijderd. Dan kun je ervoor kiezen om de rechten zodanig in te stellen dat er alleen records gelezen, ingevoerd, gewijzigd en verwijder mogen worden.

Alle andere mogelijke opties kun je dan uitschakelen. Op deze manier zou de data iets veiliger kunnen zijn.