Hoe SQL Injection in PHP Voorkomen

Gepubliceerd 26 januari 2026

SQL injection is een groot beveiligingsprobleem waarmee aanvallers database-queries kunnen aanpassen en toegang kunnen krijgen tot privégegevens waar ze geen toegang toe zouden moeten hebben. In PHP-applicaties komen SQL injection-aanvallen voor wanneer gebruikersinvoer niet wordt gecontroleerd of opgeschoond voordat deze wordt gebruikt in SQL-queries. Dit artikel bekijkt de verschillende soorten SQL injection-aanvallen, toont voorbeelden van kwetsbare PHP-code en bespreekt de beste manieren om SQL injection-problemen in je applicaties te voorkomen.

SQL Injection-Aanvallen in PHP Begrijpen

Wat is SQL Injection?

SQL injection is een techniek die door aanvallers wordt gebruikt om kwaadaardige SQL-code in een kwetsbare PHP-applicatie te injecteren. Het gebeurt wanneer gebruikersinvoer niet goed wordt gevalideerd of opgeschoond voordat deze in een SQL-query wordt gebruikt. Aanvallers maken misbruik van ontoereikende invoervalidatie en dynamische SQL-queries om het gedrag van de applicatie te manipuleren en ongeautoriseerde toegang tot de database te krijgen. Door speciale invoerwaarden te maken, kunnen aanvallers de structuur van de SQL-query aanpassen, authenticatie omzeilen, gevoelige informatie ophalen of zelfs willekeurige SQL-commando's op de databaseserver uitvoeren.

Hier is een voorbeeld van een kwetsbaar PHP-codefragment dat SQL injection mogelijk maakt:

$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($connection, $query);

In deze code worden de door de gebruiker opgegeven $username en $password waarden direct samengevoegd in de SQL-query zonder enige validatie of opschoning. Een aanvaller kan deze kwetsbaarheid misbruiken door kwaadaardige invoer in te voeren zoals:

username: admin' --
password: anyvalue

De resulterende SQL-query zou zijn:

SELECT * FROM users WHERE username='admin' -- AND password='anyvalue'

De -- commentaarsyntax schakelt de wachtwoordcontrole uit, waardoor de aanvaller authenticatie kan omzeilen en ongeautoriseerde toegang kan krijgen.

Veelvoorkomende Soorten SQL Injection-Aanvallen

Union-based SQL Injection

Bij dit type aanval gebruiken aanvallers de UNION-operator om de resultaten van meerdere SELECT-statements te combineren en gevoelige informatie te extraheren. Door een kwaadaardig SELECT-statement aan de oorspronkelijke query toe te voegen, kunnen aanvallers gegevens ophalen uit verschillende tabellen of kolommen die niet bedoeld waren om toegankelijk te zijn. Deze techniek vertrouwt erop dat de applicatie het resultaat van de geïnjecteerde query aan de aanvaller teruggeeft.

Voorbeeld van een union-based SQL injection:

Oorspronkelijke query:

SELECT name, description FROM products WHERE id = $id

Geïnjecteerde query:

SELECT name, description FROM products WHERE id = 1 UNION SELECT username, password FROM users

De geïnjecteerde query combineert de oorspronkelijke query met een extra SELECT-statement dat gevoelige gebruikersgegevens uit de users-tabel ophaalt.

Blind SQL Injection

Blind SQL injection vindt plaats wanneer de applicatie de resultaten van de geïnjecteerde query niet direct aan de aanvaller teruggeeft. In plaats daarvan sturen aanvallers speciaal vormgegeven queries en observeren ze de reactie van de applicatie om de structuur en inhoud van de database af te leiden. Ze doen dit door de applicatie zich verschillend te laten gedragen op basis van de uitkomst van de geïnjecteerde conditie. Door de queries zorgvuldig op te bouwen en de reactietijd van de applicatie, foutmeldingen of andere gedragsveranderingen te analyseren, kunnen aanvallers gevoelige informatie stukje bij beetje extraheren.

Voorbeeld van een blind SQL injection:

Oorspronkelijke query:

SELECT * FROM users WHERE id = $id

Geïnjecteerde query:

SELECT * FROM users WHERE id = 1 AND SUBSTRING(password, 1, 1) = 'a'

In dit geval injecteert de aanvaller een conditie die controleert of het eerste teken van het wachtwoord 'a' is. Door de reactie van de applicatie te observeren (bijvoorbeeld een vertraging of een foutmelding), kan de aanvaller bepalen of de conditie waar of onwaar is en geleidelijk het wachtwoord teken voor teken raden.

Error-based SQL Injection

Bij deze techniek veroorzaken aanvallers opzettelijk SQL-fouten om informatie te verzamelen over de database en het schema. Ze doen dit door misvormde SQL-statements of speciale tekens in te voeren die ervoor zorgen dat de database foutmeldingen genereert. Deze foutmeldingen bevatten vaak waardevolle informatie zoals tabelnamen, kolomnamen of zelfs delen van gevoelige gegevens. Aanvallers kunnen deze informatie gebruiken om hun aanvallen te verfijnen en specifieke delen van de database te targeten.

Voorbeeld van een error-based SQL injection:

Geïnjecteerde query:

SELECT * FROM users WHERE id = 1 AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT (SELECT CONCAT(username,':',password)) FROM users LIMIT 0,1), 0x3a, FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)

Deze complexe geïnjecteerde query probeert de gebruikersnaam en het wachtwoord uit de users-tabel te extraheren door een fout te veroorzaken die de gevoelige informatie in de foutmelding bevat.

SQL Injection-Aanvallen Voorkomen

Om SQL injection-aanvallen in PHP-applicaties te voorkomen, volg je deze best practices:

Praktijk Beschrijving
Parameterized Queries Gebruik prepared statements met geparametriseerde queries om gebruikersinvoer te scheiden van de SQL-querystructuur.
Input Validation Valideer en schoon gebruikersinvoer op voordat je deze in SQL-queries gebruikt. Wijs speciale tekens of kwaadaardige invoer af of schoon deze op.
Least Privilege Configureer het database-gebruikersaccount met minimale rechten die nodig zijn voor de functionaliteit van de applicatie.
Escaping Special Characters Als geparametriseerde queries niet mogelijk zijn, escape dan speciale tekens in gebruikersinvoer correct met PHP's mysqli_real_escape_string() of PDO's quote()-functie.
Stored Procedures Gebruik stored procedures om database-operaties te encapsuleren en de toegang tot de onderliggende tabellen te beperken.
Prepared Statements Gebruik prepared statements om SQL-queries vooraf te compileren en gebruikersinvoer te scheiden van de querystructuur. Prepared statements behandelen gebruikersinvoer als data, niet als onderdeel van het SQL-commando.

Hier is een diagram dat de flow van een SQL injection-aanval illustreert:

graph TD A[Aanvaller] --> B[Maakt kwaadaardige invoer] B --> C[Stuurt kwaadaardige invoer naar de applicatie] C --> D[Applicatie ontvangt de invoer] D --> E{Invoer correct gevalideerd/opgeschoond?} E -->|Nee| F[SQL-query samengevoegd met kwaadaardige invoer] F --> G[Database voert de geïnjecteerde query uit] G --> H[Aanvaller krijgt ongeautoriseerde toegang of haalt gevoelige gegevens op] E -->|Ja| I[SQL-query veilig uitgevoerd] I --> J[Applicatie functioneert zoals bedoeld]

SQL Injection in PHP Voorkomen met Prepared Statements

Prepared Statements

Prepared statements voeren SQL-queries uit door de SQL-logica te scheiden van gebruikersinvoer. Ze beschermen tegen SQL injection-aanvallen. In plaats van gebruikersinvoer in de SQL-query te plaatsen, gebruiken prepared statements placeholders (bijvoorbeeld ? of named parameters zoals :username) voor de invoerwaarden. Deze placeholders worden later gebonden aan de werkelijke waarden voordat de query wordt uitgevoerd. Door de SQL-structuur te scheiden van de gebruikersinvoer, behandelen prepared statements de invoer als data en niet als onderdeel van het SQL-commando, waardoor injectie van kwaadaardige SQL-code wordt voorkomen.

PDO (PHP Data Objects) voor Prepared Statements

PDO (PHP Data Objects) is een extensie in PHP die een consistente en veilige manier biedt om met verschillende databases te werken. Het ondersteunt prepared statements en geparametriseerde queries, waardoor het een goede keuze is voor het voorkomen van SQL injection-kwetsbaarheden.

PDO biedt methoden om SQL-statements veilig voor te bereiden, te binden en uit te voeren. Het handelt de verschillen tussen database-leveranciers af, waardoor je draagbare en onderhoudbare code kunt schrijven. PDO biedt ook betere prestaties dan traditionele MySQL-functies, omdat het hetzelfde prepared statement meerdere keren kan hergebruiken met verschillende parameterwaarden.

Handleiding voor het Gebruik van Prepared Statements met PDO

Hier is een handleiding over hoe je prepared statements met PDO gebruikt om SQL injection in PHP te voorkomen:

  1. Maak verbinding met de database met PDO:

    $dsn = 'mysql:host=localhost;dbname=mydatabase';
    $username = 'your_username';
    $password = 'your_password';
    
    try {
       $pdo = new PDO($dsn, $username, $password);
       $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
       die('Connection failed: ' . $e->getMessage());
    }
  2. Bereid de SQL-query voor met placeholders voor gebruikersinvoer:

    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");

    In dit voorbeeld is :username een named parameter die dient als placeholder voor de door de gebruiker opgegeven gebruikersnaamwaarde.

  3. Bind de gebruikersinvoer aan de placeholders:

    $username = $_POST['username'];
    $stmt->bindParam(':username', $username);

    De bindParam()-methode bindt de gebruikersinvoer aan de named parameter in het prepared statement. Het behandelt de invoer als data en niet als onderdeel van het SQL-commando.

  4. Voer het prepared statement uit:

    $stmt->execute();

    De execute()-methode voert het prepared statement uit met de gebonden parameterwaarden.

  5. Haal de resultaten op en verwerk deze:

    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    De fetch()-methode haalt het resultaat van het uitgevoerde statement op. Je kunt de fetch-stijl specificeren, zoals PDO::FETCH_ASSOC, om een associatieve array te retourneren.

Example: Gebruikerslogin

Laten we een voorbeeld bekijken van een gebruikersloginsysteem waarbij we gebruikersinformatie moeten ophalen op basis van de opgegeven gebruikersnaam en wachtwoord. Hier is hoe je prepared statements met PDO kunt gebruiken om SQL injection te voorkomen:

// Haal de gebruikersinvoer op uit het loginformulier
$username = $_POST['username'];
$password = $_POST['password'];

// Bereid de SQL-query voor met placeholders
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");

// Bind de gebruikersinvoer aan de placeholders
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);

// Voer het prepared statement uit
$stmt->execute();

// Haal het gebruikersrecord op
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// Controleer of de gebruiker bestaat en het wachtwoord correct is
if ($user) {
    // Gebruiker succesvol geauthenticeerd
    // Voer acties uit (bijvoorbeeld sessie instellen, doorsturen)
} else {
    // Ongeldige gebruikersnaam of wachtwoord
    // Toon een foutmelding
}

In dit voorbeeld worden de door de gebruiker opgegeven gebruikersnaam en wachtwoord gebonden aan de placeholders in het prepared statement. De SQL-query wordt veilig uitgevoerd, waardoor potentiële SQL injection-pogingen worden voorkomen.

Voordelen van Prepared Statements

  • Scheidt SQL-logica van gebruikersinvoer, waardoor SQL injection-aanvallen worden voorkomen
  • Verbetert de leesbaarheid en onderhoudbaarheid van code
  • Maakt hergebruik van hetzelfde prepared statement mogelijk met verschillende parameterwaarden
  • Biedt betere prestaties dan traditionele MySQL-functies

Best Practices

  • Gebruik altijd prepared statements voor SQL-queries met gebruikersinvoer
  • Valideer en schoon gebruikersinvoer op voordat je deze in SQL-queries gebruikt
  • Gebruik named parameters voor leesbaarheid en onderhoudbaarheid
  • Implementeer veilige databaseconfiguratie en least privilege-toegangscontrole
  • Houd je PHP- en databasesoftware up-to-date met de nieuwste beveiligingspatches

Extra Maatregelen voor het Voorkomen van SQL Injection in PHP

Input Validatie Technieken

Naast het gebruik van prepared statements, moet je gebruikersinvoer valideren en opschonen voordat je deze in SQL-queries gebruikt. Input validatie voorkomt SQL injection-aanvallen door te controleren of de door gebruikers ingevoerde gegevens overeenkomen met het verwachte formaat en alleen toegestane tekens bevatten.

Whitelisting is een goede techniek, waarbij je een set toegestane tekens of patronen definieert en elke invoer afwijst die niet aan die regels voldoet. Als je bijvoorbeeld verwacht dat een gebruiker een numeriek ID invoert, kun je de invoer valideren met reguliere expressies om te controleren of deze alleen cijfers bevat.

Hier is een voorbeeld van het valideren van een numeriek ID met PHP:

$id = $_POST['id'];

// Valideer dat het ID alleen cijfers bevat
if (!preg_match('/^\d+$/', $id)) {
    // Ongeldige invoer, behandel de fout
    die("Ongeldig ID-formaat");
}

// Gebruik het gevalideerde ID in de SQL-query
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);

In dit voorbeeld controleert de preg_match()-functie of de $id-variabele alleen cijfers bevat. Als de validatie mislukt, wordt een fout gegenereerd, waardoor wordt voorkomen dat de ongeldige invoer in de SQL-query wordt gebruikt.

Je moet ook speciale tekens correct escapen en de juiste coderingsmethoden gebruiken. Speciale tekens zoals enkele aanhalingstekens, dubbele aanhalingstekens en backslashes kunnen worden gebruikt om SQL-queries te manipuleren als ze niet correct worden behandeld. Door deze tekens te escapen of geschikte coderingsfuncties te gebruiken, kun je voorkomen dat ze worden geïnterpreteerd als onderdeel van de SQL-syntaxis.

PHP heeft functies zoals mysqli_real_escape_string() of PDO::quote() om speciale tekens in gebruikersinvoer te escapen. Het is echter over het algemeen beter om prepared statements met geparametriseerde queries te gebruiken in plaats van handmatig escapen, omdat dit een betrouwbaardere en veiligere aanpak is.

Hier zijn enkele voorbeelden van veelvoorkomende input validatietechnieken:

Techniek Beschrijving Voorbeeld
Whitelisting Sta alleen specifieke tekens of patronen toe Sta alleen alfanumerieke tekens toe: /^[a-zA-Z0-9]+$/
Lengtevalidatie Controleer de lengte van de invoer Zorg ervoor dat gebruikersnaam tussen 4 en 20 tekens is: /^.{4,20}$/
Typevalidatie Valideer het invoertype (bijvoorbeeld integer, e-mail) Valideer een e-mailadres: /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}$/i
Bereikvalidatie Controleer of de invoer binnen een gespecificeerd bereik valt Zorg ervoor dat leeftijd tussen 18 en 120 is: /^(1[89]|[2-9]\d|1[01]\d|120)$/

Stored Procedures en Parameterized Queries

Stored procedures zijn vooraf gecompileerde SQL-statements die in de database worden opgeslagen en vanuit de applicatiecode kunnen worden aangeroepen. Ze voegen een extra beveiligingslaag toe door complexe SQL-logica te encapsuleren en de toegang tot de onderliggende tabellen te beperken.

Bij het gebruik van stored procedures kun je parameters definiëren die gebruikersinvoer accepteren, vergelijkbaar met prepared statements. Door gebruikersinvoer als parameters aan stored procedures door te geven, kun je SQL injection-aanvallen voorkomen omdat de invoer wordt behandeld als data en niet als onderdeel van het SQL-commando.

Hier is een voorbeeld van een stored procedure die gebruikersinformatie ophaalt op basis van een opgegeven gebruikersnaam:

CREATE PROCEDURE GetUserByUsername
    @Username VARCHAR(50)
AS
BEGIN
    SELECT * FROM users WHERE username = @Username
END

In dit voorbeeld accepteert de stored procedure GetUserByUsername een parameter @Username en gebruikt deze in de SQL-query om gebruikersinformatie op te halen. De parameter wordt behandeld als data, waardoor SQL injection wordt voorkomen.

Om de stored procedure vanuit PHP aan te roepen, kun je PDO of MySQLi gebruiken:

$username = $_POST['username'];

$stmt = $pdo->prepare("CALL GetUserByUsername(?)");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

Door stored procedures met geparametriseerde queries te gebruiken, kun je de database-toegangslogica centraliseren, de prestaties verbeteren en de beveiliging van je PHP-applicatie versterken.

Least Privilege Principe en Database Gebruikersrollen

Het principe van least privilege houdt in dat gebruikers en applicaties alleen de minimale rechten moeten hebben die nodig zijn om hun taken uit te voeren. Het toepassen van dit principe op database-gebruikersrollen en -rechten is een belangrijke stap in het voorkomen van SQL injection en het verminderen van de impact van mogelijke aanvallen.

Bij het configureren van de database-gebruiker voor je PHP-applicatie, beperk je de toegangsrechten tot het minimum dat nodig is voor de functionaliteit van de applicatie. Dit betekent dat je de gebruiker alleen de rechten geeft die nodig zijn om specifieke taken uit te voeren, zoals SELECT, INSERT, UPDATE of DELETE, op de relevante tabellen.

Door de rechten van de database-gebruiker te beperken, kun je de potentiële schade verminderen die een aanvaller kan veroorzaken als ze erin slagen een SQL injection-kwetsbaarheid te misbruiken. Als de applicatie bijvoorbeeld alleen leestoegang nodig heeft tot bepaalde tabellen, moet de database-gebruiker alleen SELECT-rechten krijgen op die tabellen, waardoor ongeautoriseerde wijzigingen of verwijderingen worden voorkomen.