SQL-injectie

Uit Wikipedia, de vrije encyclopedie
Naar navigatie springen Naar zoeken springen

De term SQL-injectie (Engels: SQL injection) wordt gebruikt voor een type kwetsbaarheid van computerapplicaties, meestal webapplicaties. Applicaties die informatie in een database opslaan maken vaak gebruik van SQL om met de database te communiceren. SQL-injectie kan gebeuren als invoer van gebruikers (op onvoldoende gecontroleerde wijze) wordt verwerkt in een SQL-statement. Om de precieze werking van SQL-injectie te begrijpen is het belangrijk om te weten hoe SQL werkt.

Rol van de apostrof in SQL[bewerken | brontekst bewerken]

In SQL heeft de apostrof een belangrijke functie, namelijk het afbakenen van niet-numerieke gegevens. Om bijvoorbeeld alle personen met de naam "Jansen" te selecteren uit een tabel wordt het volgende statement gebruikt

 SELECT * FROM persoon WHERE achternaam = 'Jansen'

In een applicatie waar gezocht kan worden naar personen, zal de gebruiker in het zoekveld uitsluitend "Jansen" invullen. In de applicatie wordt op basis van deze invoer bovenstaande code naar de database gestuurd.

Interessant wordt het als de gebruiker een apostrof in het zoekveld invult, bijvoorbeeld "'t Hart". In een correct statement moet dan namelijk de apostrof worden verdubbeld, of voorzien van een backslash (\).

 SELECT * FROM persoon WHERE achternaam = '''t Hart'
 SELECT * FROM persoon WHERE achternaam = '\'t Hart'

Als dat niet gebeurt, dan levert het een incorrect SQL-statement op, en volgt een foutmelding van de database. Maar het betekent ook dat de applicatie niet beschermd is tegen SQL-injectie.

SQL-injectie[bewerken | brontekst bewerken]

SQL-injectie bestaat er uit dat een gebruiker in het invoerveld tekens invoert die ervoor zorgen dat een ongewenste SQL-query wordt uitgevoerd. Daarbij wordt vaak gebruikgemaakt van de apostrof. Dit kan alleen als bij het genereren van de SQL-code op basis van gebruikersinvoer de apostrof niet goed wordt afgevangen.

De gebruiker typt bijvoorbeeld "Jansen' OR 'a' = 'a" in het zoekveld. Het resulterende statement is dan

 SELECT * FROM persoon WHERE achternaam = 'Jansen' OR 'a' = 'a';

Omdat "'a' = 'a'" altijd waar is, voldoet nu elk record aan de gestelde voorwaarde.

Met bovenstaand voorbeeld kan de hacker extra informatie ophalen uit de database. Dezelfde methode levert soms ook de mogelijkheden om nieuwe informatie aan de database toe te voegen, bestaande informatie aan te passen en informatie te verwijderen. Daarvoor is informatie nodig over de structuur (namen van tabellen en kolommen) van de database. De naamgeving van tabellen en kolommen is meestal logisch om werken met de database voor een reguliere gebruiker eenvoudig te houden en daardoor voorspelbaar. Daarnaast kan de hacker ook diverse zaken uitproberen om bijvoorbeeld een gebruikersaccount met beheerders-rechten aan te maken. Lukt dit, dan kan de hacker de totale controle over de webserver overnemen, met alle gevolgen van dien.

Blinde SQL-injectie[bewerken | brontekst bewerken]

Met blinde SQL-injectie wordt bedoeld dat de database geen reactie teruggeeft. Wanneer in het vorige voorbeeld de applicatie geen informatie terug zou geven, bijvoorbeeld omdat het alleen controleert of de naam al in de database voorkomt, spreken we van een blind SQL-injectie. Een hacker kan echter aan het gedrag van de server afleiden of bepaalde data in de database staat. Dit kan grofweg op drie manieren: boolean based, error based of time based[1].

Boolean gebaseerd[bewerken | brontekst bewerken]

In het geval van een boolean gebaseerde SQL-injectie is het doel om de applicatie te laten antwoorden met 'ja' of 'nee'. Een aanvaller kan de query dan bewerken naar iets als: bestaat de naam 'Jansen' en bestaat er in de gebruikerstabel een gebruiker met de naam admin? Een dergelijke query ziet er dan als volgt uit:

 SELECT * FROM persoon WHERE achternaam = 'Jansen' AND 1 IN (select 1 from users where username like 'admin');

Wanneer de aanvaller alle gebruikers uit de database zou willen halen, zou hij dit kunnen bereiken door de vraag te specificeren:

' or 1 in (select 1 from users where username like 'a%');--  #positief resultaat, dus gebruiker met a% bestaat
' or 1 in (select 1 from users where username like 'a%');-- #positief resultaat, dus gebruiker met a% bestaat
' or 1 in (select 1 from users where username like 'aa%');-- #positief resultaat, dus gebruiker met aa% bestaat
' or 1 in (select 1 from users where username like 'aaa%');-- #negatief resultaat, dus geen gebruiker met aaa
' or 1 in (select 1 from users where username like 'aab%');-- #negatief resultaat, op naar aac

Een dergelijke methode vergt veel verschillende queries, waardoor er in de praktijk vaak gebruik wordt gemaakt van gespecialiseerde tools of eigen scripts die dit kunnen automatiseren.

Error gebaseerd[bewerken | brontekst bewerken]

Als er niet een duidelijk 'Ja' of 'Nee' is, kan een aanvaller deze proberen te verkrijgen door de database opzettelijk een foutmelding te laten genereren. Vaak wordt hiervoor een conditie gebruikt waarbij de database in een positief geval laat delen door het getal nul. Omdat dit niet kan zal de applicatie een database-error geven. In het voorbeeld hieronder wordt gecontroleerd of de tabel 'users' bestaat. Als dit zo is treedt er een database-error op, anders niet.

 SELECT * FROM persoon WHERE achternaam = 'Jansen' AND 1=(SELECT CASE WHEN (TABLE_NAME='users') THEN 1/0 ELSE 1 END FROM information_schema.TABLES);

Tijd gebaseerd[bewerken | brontekst bewerken]

In het geval er geen duidelijke error is, kan een aanvaller proberen om het resultaat van de database uit te stellen. Dit gebeurt met de sleep()-functie, waarmee de database de opdracht krijgt om een aantal seconden te wachten. Het bovenstaande voorbeeld in een tijd-gebaseerde blind SQL-injectie, waarbij er in plaats van een error een vertraging plaatsvindt van 5 seconden, ziet er als volgt uit:

 SELECT * FROM persoon WHERE achternaam = 'Jansen' AND 1=(SELECT CASE WHEN (TABLE_NAME='users') THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES);

Preventie[bewerken | brontekst bewerken]

Door middel van het geven van de minimaal noodzakelijke rechten van de gebruiker en de SQL-server kan het ongewenst aanpassen van gegevens en het uitvoeren van ongewenste commando's op het systeem moeilijker gemaakt worden.

Afwijzen van verkeerde invoer[bewerken | brontekst bewerken]

Men kan alleen bepaalde tekens en strings toestaan in de invoer en alles wat een betekenis heeft in een SQL-commando afwijzen (bijvoorbeeld de tekens en strings: insert drop ' -- ; enzovoorts, maar ook char zodat niet via een omweg een ' in de invoer kan voorkomen). Zuiver numerieke gebruikersnamen en wachtwoorden moeten afgewezen worden, omdat die automatisch omgezet kunnen worden in strings met ongewenste betekenissen.

Backslash[bewerken | brontekst bewerken]

De injectie met SQL-code kan eenvoudig tegengegaan worden door het juist verwerken van informatie die door een gebruiker wordt aangeleverd. In de programmeertaal PHP kan dat bijvoorbeeld via mysqli_real_escape_string(). Deze functie vangt (my)SQL-specifieke karakters af door er een backslash (\) voor te plaatsen. Hierdoor weet het systeem dat enkel het letterteken bedoeld wordt, en niet meer de scheidende functie van het afbakenen van gegevens. Een stukje voorbeeld-programmeertaal in PHP kan er als volgt uitzien:

 <?php
 $result = mysqli_query($verbinding, "SELECT * FROM persoon WHERE achternaam = '" . mysqli_real_escape_string($verbinding, $_POST['achternaam']) . "'");
 ?>

In de programmeertaal Javascript (NodeJS) zou men gebruik kunnen maken van het volgende voorbeeld:

const result = await verbinding.query('SELECT * FROM persoon WHERE achternaam = ?', [req.body.achternaam]);

In het bovenstaande voorbeeld wordt het vraagteken vervangen door de variabel die in een array wordt meegegeven en wordt de waarde "escaped". Wanneer men meerdere condities zou willen toevoegen, zou men simpelweg gebruik kunnen maken van:

const result = await verbinding.query('SELECT * FROM persoon WHERE voornaam = ? AND achternaam = ?', [req.body.voornaam, req.body.achternaam]);

Statement[bewerken | brontekst bewerken]

Een andere methode om injectie tegen te gaan is door middel van een voorgedefinieerd statement. Hierbij wordt in het aanroepende programma het statement opgebouwd met een variabele. De inhoud van de variabele wordt dan gekoppeld aan de gebruikersinvoer.

Bijvoorbeeld (in de taal Java):

In plaats van

  1. Connection con = (maak verbinding met de database)
  2. Statement stmt = con.createStatement();
  3. ResultSet rset = stmt.executeQuery("SELECT * FROM persoon WHERE achternaam = '" + invoer + "';");

is het beter om het volgende te gebruiken

  1. Connection con = (maak verbinding met de database)
  2. PreparedStatement pstmt = con.prepareStatement("SELECT * FROM persoon WHERE achternaam = ?");
  3. pstmt.setString(1, invoer);
  4. ResultSet rset = pstmt.executeQuery();

Databasepermissies[bewerken | brontekst bewerken]

Schade kan beperkt worden door alleen de strikt nodige permissies te verlenen. Bijvoorbeeld kan men het op SQL-server onmogelijk maken bepaalde tabellen te lezen:

deny select on sys.sysobjects to webdatabaselogon;
deny select on sys.objects to webdatabaselogon;
deny select on sys.tables to webdatabaselogon;
deny select on sys.views to webdatabaselogon;

Externe link[bewerken | brontekst bewerken]