SQL-injecties vormen een aan­zien­lij­ke be­drei­ging voor re­la­ti­o­ne­le da­ta­ba­se­mo­del­len en de gevoelige in­for­ma­tie die ze bevatten. Daarom is uit­ge­brei­de be­scher­ming tegen deze on­ge­oor­loof­de externe toegangs pogingen – mogelijk gemaakt door be­vei­li­gings­lek­ken – absoluut es­sen­ti­eel.

Wat is een SQL-injectie?

Een SQL-injectie is een type aanval dat misbruik maakt van een be­vei­li­gings­lek in re­la­ti­o­ne­le da­ta­ba­se­sys­te­men die de SQL -querytaal gebruiken om ge­brui­kers­in­voer te verwerken. De aanvaller maakt misbruik van ge­brui­kers­in­voer die niet goed is geëscape en speciale tekens bevat, zoals dubbele kop­pel­te­kens, aan­ha­lings­te­kens of puntkomma’s. Deze tekens hebben speciale functies voor de SQL-in­ter­pre­ter en maken het mogelijk om de uit­ge­voer­de commando’s extern te ma­ni­pu­le­ren. SQL-injecties worden vaak ge­as­so­ci­eerd met PHP- en ASP-toe­pas­sin­gen die ge­bruik­ma­ken van ver­ou­der­de in­ter­fa­ces. In veel van deze gevallen wordt de invoer niet voldoende gezuiverd, waardoor deze een be­lang­rijk doelwit voor aanvallen vormt.

Door stra­te­gisch func­tie­ka­rak­ters in te voegen, kan een on­be­voeg­de gebruiker extra SQL-op­drach­ten invoegen en database-items ma­ni­pu­le­ren om gegevens te lezen, te wijzigen of te ver­wij­de­ren. In ernstige gevallen kunnen aan­val­lers zelfs toegang krijgen tot de op­dracht­re­gel van het systeem, waardoor ze volledige controle over de da­ta­ba­se­ser­ver kunnen krijgen.

Voorbeeld van SQL-injectie dat laat zien hoe een database-aanval werkt

Omdat kwetsbare da­ta­ba­se­ser­vers snel kunnen worden ge­ï­den­ti­fi­ceerd en SQL-in­jec­tie­aan­val­len relatief eenvoudig uit te voeren zijn, blijft deze methode een van de meest gebruikte tech­nie­ken onder cy­ber­cri­mi­ne­len we­reld­wijd. Aan­val­lers gebruiken ver­schil­len­de stra­te­gie­ën en maken daarbij misbruik van zowel nieuw ontdekte als al lang bestaande be­vei­li­gings­fou­ten in de ap­pli­ca­ties die betrokken zijn bij het ge­ge­vens­be­heer­pro­ces. Om beter te begrijpen hoe SQL-injectie in de praktijk werkt, bekijken we twee veel­voor­ko­men­de aan­vals­me­tho­den als voorbeeld.

Voorbeeld 1: Toegang via slecht ge­ës­ca­peer­de ge­brui­kers­in­voer

Om toegang te krijgen tot een database moeten ge­brui­kers zich meestal eerst au­then­ti­ce­ren. Hiervoor worden vaak scripts gebruikt om een in­log­for­mu­lier weer te geven met velden voor een ge­brui­kers­naam en wacht­woord. Ge­brui­kers vullen het formulier in en het script con­tro­leert ver­vol­gens of er over­een­ko­men­de gegevens in de database staan. Standaard bevat de database mogelijk een tabel met de naam users en de kolommen username en password. In een typische we­bap­pli­ca­tie kunnen de relevante script­re­gels voor da­ta­ba­setoe­gang (met behulp van Python-achtige pseu­do­co­de) er als volgt uitzien:

uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)
python

Een aanvaller kan nu het wacht­woord­veld ma­ni­pu­le­ren met behulp van een SQL-injectie, bij­voor­beeld door password' OR 1='1 in te voeren, wat re­sul­teert in de volgende SQL-query:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
python

Hierdoor krijgt de aanvaller volledige toegang tot de volledige ge­brui­kersta­bel in de database, aangezien de wacht­woord­voor­waar­de altijd als waar wordt be­oor­deeld (1='1'). Als de aanvaller zich aanmeldt als beheerder, kan hij alle database-items vrijelijk wijzigen. Als al­ter­na­tief kan het veld voor de ge­brui­kers­naam op dezelfde manier worden ge­ma­ni­pu­leerd.

Voorbeeld 2: Ge­ge­vens­ver­za­me­ling via ID-ma­ni­pu­la­tie

Het opvragen van in­for­ma­tie uit een database op basis van een ID is een prak­ti­sche en veel­ge­bruik­te methode, maar opent ook een mogelijke deur voor SQL-injectie. Een webserver weet bij­voor­beeld via een verzonden ID-detail in een URL welke in­for­ma­tie hij uit de database moet ophalen. Het bij­be­ho­ren­de PHP-script ziet er als volgt uit:

<?php
    $mysqli = new mysqli("localhost", "username", "password", "database");
    $id = intval($_GET['id']);
    $result = $mysqli->query("SELECT * FROM table WHERE id=$id");
    while ($row = $result->fetch_assoc()) {
        echo print_r($row, true);
    }
?>
php

De verwachte URL volgt het patroon .../script.php?id=22. In dit geval zou de ta­bel­ver­mel­ding met ID ‘22’ worden opgehaald. Als een on­be­voeg­de persoon de mo­ge­lijk­heid heeft om deze URL te ma­ni­pu­le­ren en in plaats daarvan een verzoek zoals .../script.php?id=22+OR+1=1 verstuurt, zal de re­sul­te­ren­de query ervoor zorgen dat alle rijen in de tabel worden opgehaald:

SELECT * FROM table WHERE id=22 OR 1=1;
sql

Hoe vinden cri­mi­ne­len kwetsbare da­ta­ba­se­sys­te­men?

In principe kan elke website of we­bap­pli­ca­tie die ge­bruik­maakt van SQL-databases zonder voor­be­rei­de query’s (prepared sta­te­ments) of andere be­scher­men­de maat­re­ge­len kwetsbaar zijn voor SQL-injecties. Ontdekte kwets­baar­he­den blijven niet lang verborgen op het we­reld­wij­de web. Er zijn zelfs websites die actuele lijsten met bekende be­vei­li­gings­fou­ten pu­bli­ce­ren en zelfs uitleggen hoe aan­val­lers Google-zoek­op­drach­ten kunnen gebruiken om over­een­ko­men­de web­pro­jec­ten te vinden. Als een website ge­de­tail­leer­de SQL-fout­mel­din­gen re­tour­neert, kunnen cy­ber­cri­mi­ne­len die meldingen gebruiken om po­ten­ti­ë­le kwets­baar­he­den te iden­ti­fi­ce­ren. Het toevoegen van een apostrof aan het einde van een URL met een ID-parameter kan bij­voor­beeld al een zwakke plek bloot­leg­gen, zoals te zien is in het volgende voorbeeld:

[DomainName].com/news.php?id=5’

Een kwetsbare website stuurt een fout­mel­ding terug in de volgende vorm:

Query failed: You have an error in your SQL syntax…

Soort­ge­lij­ke methoden kunnen ook worden gebruikt om het aantal kolommen, tabel- en ko­lom­na­men, de SQL-versie of zelfs ge­brui­kers­na­men en wacht­woor­den te ach­ter­ha­len. Daarnaast bestaan er ver­schil­len­de tools die zowel het de­tec­tie­pro­ces als de uit­voe­ring van SQL-in­jec­tie­aan­val­len kunnen au­to­ma­ti­se­ren.

Hoe u uw database kunt be­scher­men tegen SQL-injectie

Er zijn ver­schil­len­de methoden die u kunt gebruiken om SQL-in­jec­tie­aan­val­len op uw da­ta­ba­se­sys­teem te voorkomen. U moet alle betrokken com­po­nen­ten aanpakken: de server, de af­zon­der­lij­ke ap­pli­ca­ties en het da­ta­ba­se­be­heer­sys­teem.

Stap 1: Con­tro­leer ge­au­to­ma­ti­seer­de invoer vanuit ap­pli­ca­ties

Bij het verwerken van invoer van externe of in­ge­bouw­de ap­pli­ca­ties is het es­sen­ti­eel om de in­ge­dien­de waarden te valideren en te filteren om SQL-injecties te voorkomen.

1. Ge­ge­vens­soor­ten valideren

Elke invoer moet over­een­ko­men met het verwachte ge­ge­vens­ty­pe. Als er bij­voor­beeld een numerieke invoer vereist is, zou een een­vou­di­ge validatie in PHP er als volgt kunnen uitzien:

if (filter_var($input, FILTER_VALIDATE_INT) === false) {
    throw new InvalidArgumentException("Invalid input");
}
php

Soort­ge­lij­ke controles moeten worden uit­ge­voerd voor strings, datums of andere spe­ci­fie­ke formaten.

2. Speciale tekens filteren

Speciale tekens kunnen be­vei­li­gings­ri­si­co’s opleveren, vooral in SQL- of HTML-contexten. Een veilige aanpak is om htmlspecialchars() te gebruiken voor HTML-invoer en PDO::quote() voor SQL-query’s.

3. Vermijd het weergeven van fout­mel­din­gen

Directe fout­mel­din­gen die tech­ni­sche details over de database of het systeem onthullen, moeten worden vermeden. Geef in plaats daarvan een algemene melding weer, zoals:

echo "An error occurred. Please try again later.";
error_log("Unexpected error encountered. See system log for details.");
php

4. Gebruik voor­be­rei­de ver­kla­rin­gen

Een van de meest ef­fec­tie­ve manieren om SQL-injecties te voorkomen, is door gebruik te maken van voor­be­rei­de sta­te­ments. Bij deze aanpak worden SQL-commando’s en pa­ra­me­ters af­zon­der­lijk verzonden, zodat kwaad­aar­di­ge code niet kan worden uit­ge­voerd. Hier volgt een voorbeeld van een im­ple­men­ta­tie in PHP met behulp van PDO (PHP Data Objects):

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();
php

Het da­ta­ba­se­be­heer­sys­teem zorgt er au­to­ma­tisch voor dat de invoer veilig wordt verwerkt.

Stap 2: Zorg voor uit­ge­brei­de ser­ver­be­vei­li­ging

De be­vei­li­ging van de server waarop uw da­ta­ba­se­be­heer­sys­teem draait, speelt ook een cruciale rol bij het voorkomen van SQL-injectie. Een be­lang­rij­ke maatregel is het ver­ster­ken van het be­stu­rings­sys­teem door deze best practices te volgen:

  • In­stal­leer of schakel alleen de ap­pli­ca­ties en services in die es­sen­ti­eel zijn voor het uitvoeren van de database.
  • Verwijder alle on­ge­bruik­te of onnodige ge­brui­kers­ac­counts.
  • Zorg ervoor dat alle relevante systeem- en software-updates on­mid­del­lijk worden ge­ïn­stal­leerd.
  • Pas het principe van minimale rechten toe om ervoor te zorgen dat ge­brui­kers en services alleen de minimaal nood­za­ke­lij­ke rechten krijgen.

Af­han­ke­lijk van de be­vei­li­gings­ver­eis­ten van uw web­pro­ject, moet u aan­vul­len­de be­scher­men­de maat­re­ge­len overwegen:

  • Intrusion Detection Systems (IDS) en Intrusion Pre­ven­ti­on Systems (IPS): Deze systemen gebruiken ver­schil­len­de de­tec­tie­me­tho­den om aanvallen vroeg­tij­dig te iden­ti­fi­ce­ren, waar­schu­win­gen te geven en – bij gebruik van IPS – au­to­ma­tisch te­gen­maat­re­ge­len te nemen.
  • Ap­pli­ca­ti­on Layer Gateway (ALG): Een ALG con­tro­leert en filtert het verkeer tussen ap­pli­ca­ties en web­brow­sers recht­streeks op ap­pli­ca­tie­ni­veau.
  • Web Ap­pli­ca­ti­on Firewall (WAF): Een WAF beschermt we­bap­pli­ca­ties specifiek tegen SQL-injectie en Cross-Site Scripting (XSS) door verdachte verzoeken te blokkeren of te zuiveren.
  • Zero Trust-be­na­de­ring: Dit moderne be­vei­li­gings­mo­del zorgt ervoor dat elke toegangs poging – ongeacht de herkomst ervan – wordt ge­ve­ri­fi­eerd en ge­au­then­ti­ceerd voordat toegang wordt verleend.
  • Fire­wall­re­gels en net­werk­seg­men­ta­tie: deze zijn es­sen­ti­eel om het aan­vals­op­per­vlak op lange termijn te mi­ni­ma­li­se­ren.
  • Re­gel­ma­ti­ge IT-be­vei­li­gings­au­dits en pe­ne­tra­tie­tests: deze helpen kwets­baar­he­den in een vroeg stadium op te sporen en te verhelpen.

Stap 3: Versterk de database en gebruik veilige code

Net als uw be­stu­rings­sys­teem moet uw database worden ontdaan van alle over­bo­di­ge com­po­nen­ten en up-to-date worden gehouden. Verwijder alle op­ge­sla­gen pro­ce­du­res die u niet nodig hebt en schakel alle on­ge­bruik­te services en ge­brui­kers­ac­counts uit. Maak een speciaal da­ta­ba­se­ac­count aan dat uit­slui­tend bedoeld is voor webtoe­gang en wijs hieraan alleen de minimaal vereiste mach­ti­gin­gen toe.

In over­een­stem­ming met het gebruik van voor­be­rei­de sta­te­ments wordt sterk aan­be­vo­len om de mysql PHP-module (die in PHP 7 is ver­wij­derd) niet te gebruiken. Kies in plaats daarvan voor mysqli of PDO om een betere be­vei­li­ging en com­pa­ti­bi­li­teit te ga­ran­de­ren. Een veilige mysqli query zou er als volgt uit kunnen zien:

$mysqli = new mysqli("localhost", "username", "password", "database");
if ($mysqli->connect_error) die("Connection failed");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
    echo "Login successful";
} else {
    echo "Invalid login credentials";
}
$stmt->close();
$mysqli->close();
php

Wacht­woor­den mogen nooit recht­streeks in een database worden op­ge­sla­gen of in platte tekst worden opgehaald. Gebruik in plaats daarvan een hash­me­tho­de zoals password_hash() in com­bi­na­tie met password_verify() om in­log­ge­ge­vens veilig te be­scher­men. Een veilige im­ple­men­ta­tie zou er als volgt uit kunnen zien:

$mysqli = new mysqli("localhost", "username", "password", "database");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
    echo "Login successful!";
} else {
    echo "Incorrect username or password.";
}
php

Wat hebben bobby tables te maken met SQL-injectie?

De website bobby-tables.com gebruikt een xkcd-webcomic om op hu­mo­ris­ti­sche wijze de gevaren van onveilige ge­brui­kers­in­voer in databases te il­lu­stre­ren. In de strip krijgt een moeder een te­le­foon­tje van de school van haar zoon (lief­ko­zend Little Bobby Tables genoemd). De beller vraagt of haar zoon echt Robert'); DROP TABLE Students;-- heet, waarop zij be­ves­ti­gend antwoordt. De reden voor het te­le­foon­tje wordt al snel duidelijk: door Robert in de stu­den­ten­da­ta­ba­se in te voeren, werd de hele stu­den­ten­ta­bel gewist. De moeder is niet erg begripvol – ze hoopt alleen dat de school zijn lesje heeft geleerd en voortaan de database-invoer zal opschonen.

De strip laat duidelijk zien welke ramp­za­li­ge gevolgen het kan hebben als je de invoer van ge­brui­kers in da­ta­ba­setoe­pas­sin­gen niet goed valideert en opschoont.

Ga naar hoofdmenu