Hoofdstuk 24: SQL-injectie Preventie

← Terug naar overzicht

Wat is SQL-injectie?

SQL-injectie is een van de gevaarlijkste beveiligingslekken in webapplicaties. Het ontstaat wanneer gebruikersinvoer direct in een SQL-query wordt geplaatst zonder bescherming. Een aanvaller kan dan eigen SQL-code invoegen en je database manipuleren.

<?php
// GEVAARLIJK - NOOIT DOEN!
$naam = $_POST['naam']; // Stel: gebruiker voert in: ' OR 1=1 --
$sql = "SELECT * FROM gebruikers WHERE naam = '$naam'";
// Resultaat: SELECT * FROM gebruikers WHERE naam = '' OR 1=1 --'
// Dit geeft ALLE gebruikers terug!
?>

De aanvaller sluit het aanhalingsteken, voegt OR 1=1 toe (altijd waar), en gebruikt -- om de rest van de query als commentaar te markeren.

SQL-injectie kan leiden tot: het lekken van alle data, het wijzigen of verwijderen van data, en in sommige gevallen zelfs het overnemen van de hele server. Dit is de #1 beveiligingsfout in webapplicaties.

Let op: In de oefeningen hieronder hoef je GEEN <?php en ?> tags te typen. Het platform regelt dit automatisch. Je typt alleen de PHP-code zelf.

Oefening 1

Bekijk de volgende onveilige code en begrijp het probleem. Maak een variabele $invoer met de waarde "' OR 1=1 --". Bouw de onveilige query "SELECT * FROM users WHERE naam = '$invoer'" en sla deze op in $query. Toon de query met echo zodat je ziet hoe de injectie werkt.

Jouw code:

Oefening 2

Maak een variabele $invoer met de waarde "'; DROP TABLE users; --". Bouw de onveilige query "SELECT * FROM users WHERE naam = '$invoer'". Toon de query. Dit laat zien hoe een aanvaller een hele tabel kan verwijderen.

Jouw code:

Prepared statements als oplossing

De oplossing is simpel: gebruik altijd prepared statements. Hierbij worden de query-structuur en de data strikt gescheiden. PDO verwerkt de data als pure waarden, niet als SQL-code.

<?php
// VEILIG - prepared statement
$stmt = $pdo->prepare("SELECT * FROM gebruikers WHERE naam = :naam");
$stmt->execute([':naam' => $invoer]);
// De invoer wordt als data behandeld, nooit als SQL-code
?>

Het maakt niet uit wat de gebruiker invoert — de database zal het altijd als een waarde behandelen, nooit als SQL-instructies.

Oefening 1

Herschrijf de onveilige query "SELECT * FROM users WHERE email = '$email'" naar een veilige versie met prepare() en execute(). Gebruik de named placeholder :email. Toon "Veilige query geschreven".

Jouw code:

Oefening 2

Herschrijf de onveilige login-query "SELECT * FROM users WHERE naam = '$naam' AND wachtwoord = '$wachtwoord'" naar een veilige versie met twee named placeholders. Toon "Veilige login-query geschreven".

Jouw code:

Oefening 3

Schrijf een veilige INSERT-query om een nieuwe gebruiker toe te voegen met kolommen naam, email en rol. Gebruik named placeholders. Voer uit met de waarden "Jan", "jan@email.nl", "gebruiker". Toon "Gebruiker veilig toegevoegd".

Jouw code:

Oefening 4

Schrijf een veilige zoekfunctie zoekGebruikers($pdo, $zoekterm) die gebruikers zoekt met LIKE. De wildcard % hoort in de execute-array, NIET in de query. Return de resultaten. Toon "Zoekfunctie geschreven".

Jouw code:

Veelgemaakte fouten

Zelfs met PDO kun je nog fouten maken. Hier zijn de meest voorkomende valkuilen:

<?php
// FOUT 1: Variabele direct in prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = $id"); // FOUT!

// GOED:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([':id' => $id]);

// FOUT 2: Tabelnaam of kolomnaam als placeholder
$stmt = $pdo->prepare("SELECT * FROM :tabel"); // WERKT NIET!

// GOED: Gebruik een whitelist voor dynamische tabel/kolomnamen
$toegestaan = ['users', 'producten', 'bestellingen'];
if (in_array($tabel, $toegestaan)) {
    $stmt = $pdo->prepare("SELECT * FROM $tabel");
}
?>

Onthoud: placeholders werken alleen voor waarden, niet voor tabel- of kolomnamen. Voor dynamische tabel- of kolomnamen gebruik je een whitelist.

Oefening 1

Schrijf een functie veiligeTabel($tabel) die controleert of de tabelnaam in de whitelist ["users", "producten", "bestellingen"] staat. Zo ja, return de tabelnaam. Zo nee, gooi een Exception met "Ongeldige tabel". Test met "producten" en toon het resultaat.

Jouw code:

Oefening 2

Schrijf een functie veiligeSortering($kolom, $richting) die controleert of de kolom in ["naam", "prijs", "datum"] staat en de richting "ASC" of "DESC" is. Return de ORDER BY-clausule als string, of gooi een Exception. Test met "prijs" en "DESC".

Jouw code:

Oefening 3

Bouw een complete veilige zoekpagina. Schrijf een functie zoekProducten($pdo, $zoekterm, $sorteerKolom, $richting) die: 1) de zoekterm veilig verwerkt met een prepared statement en LIKE, 2) de sorteerkolom valideert tegen ["naam", "prijs"], 3) de richting valideert tegen ["ASC", "DESC"]. Als validatie faalt, gebruik standaard "naam" en "ASC". Toon "Veilige zoekpagina geschreven".

Jouw code:

Samenvatting