aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Webontwikkeling

SQL-injectie: wat het is en hoe je het voorkomt

SQL-injectie (SQL injection) is een van de oudste en meest verwoestende aanvallen op webapplicaties; het ontstaat wanneer gegevens van de gebruiker rechtstreeks in een SQL-query worden vermengd. Door tekst te sturen die de betekenis van de query verandert in plaats van een normale waarde, kan een aanvaller gegevens lezen, wijzigen of zelfs hele tabellen verwijderen. Het goede nieuws: met de juiste patronen kun je deze hele klasse van aanvallen vrijwel volledig afsluiten. In dit artikel zien we hoe het werkt aan de hand van echte voorbeelden en bouwen we stap voor stap de belangrijkste verdedigingslinie op — prepared statements (geparametriseerde query's).

Hoe werkt SQL-injectie?

Het kernprobleem is dat de code data en commando in dezelfde string samenvoegt. De database interpreteert de hele tekst die hij ontvangt; hij kan onmogelijk weten welk deel "vertrouwde code" is en welk deel "gebruikersdata". Een klassiek, gebrekkig voorbeeld:

// DOE DIT NOOIT
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = $db->query($sql);

Als de gebruiker een normaal adres typt, is er geen probleem. Maar als de invoer ' OR '1'='1 is, wordt de query:

SELECT * FROM users WHERE email = '' OR '1'='1'

Omdat '1'='1' altijd waar is, geeft de query elke gebruiker terug. Op een inlogscherm betekent dat inloggen zonder wachtwoord.

Echte aanvalsvoorbeelden

SQL-injectie komt niet in één vorm voor. De meest voorkomende types herkennen verklaart waarom simpele trucs zoals "aanhalingstekens verwijderen" tekortschieten.

  • Authenticatie omzeilen: het patroon OR '1'='1 hierboven is het bekendste voorbeeld op inlogformulieren.
  • UNION-gebaseerd: de aanvaller voegt zijn eigen SELECT-resultaat toe aan de bestaande query. Met bijvoorbeeld ' UNION SELECT username, password FROM users -- kan data uit een andere tabel worden gelekt.
  • Error-gebaseerd: gedetailleerde foutmeldingen die de database teruggeeft, onthullen de tabel- en kolomstructuur; de aanvaller gebruikt ze om het schema in kaart te brengen.
  • Blinde (blind) injectie: zelfs als de applicatie geen fouten toont, kan data bit voor bit worden geëxtraheerd door te kijken naar waar/onwaar-antwoorden of de responstijd (zoals AND SLEEP(5)).

Een belangrijk punt: proberen de invoer te "schonen" door aanhalingstekens te verwijderen is een broze verdediging. Verschillende databases hebben verschillende escaperegels en encodingtrucs; vroeg of laat blijft er een gat over. De juiste oplossing is data volledig van code te scheiden.

De belangrijkste verdediging: prepared statements

Een prepared statement (geparametriseerde query) scheidt de structuur van de query van zijn data. Eerst bereid je het querysjabloon voor met plaatshouders, daarna stuur je de waarden via een apart kanaal. Wat de waarde ook is, de database behandelt hem als een pure waarde, nooit als een commando. In PHP met PDO:

$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();

Hier is ? een plaatshouder. De waarde die je aan execute() meegeeft, wordt ingevoegd nadat de querystructuur al is vergrendeld; of die nu een aanhalingsteken, een OR of een -- bevat, verandert niets. Benoemde parameters verbeteren de leesbaarheid:

$stmt = $pdo->prepare(
    'INSERT INTO posts (title, user_id) VALUES (:title, :user_id)'
);
$stmt->execute([':title' => $title, ':user_id' => $userId]);

Als je PDO gebruikt, zet emulatie uit om zeker te zijn dat de driver echte prepared statements gebruikt:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Hetzelfde principe geldt in elke taal. In Node.js met mysql2:

const [rows] = await conn.execute(
  'SELECT id, name FROM users WHERE email = ?',
  [email]
);

Er is één sleutelregel: plak gebruikersdata nooit met stringconcatenatie in een query. Geef het altijd als parameter door.

ORM's en query builders

Moderne frameworks genereren automatisch geparametriseerde query's wanneer ze correct worden gebruikt. In Laravel binden Eloquent en de query builder waarden vanzelf als parameters:

// Veilig — waarde wordt automatisch gebonden
User::where('email', $email)->first();

DB::table('users')->where('email', $email)->get();

Maar een ORM beschermt je niet automatisch; zodra je ruwe SQL schrijft, verschuift de verantwoordelijkheid weer naar jou. Als je gebruikersdata rechtstreeks in DB::raw() of een whereRaw()-argument zet, ontstaat het gat opnieuw. Gebruik ook daar bindings:

// Fout: waarde direct ingebed
DB::select("SELECT * FROM users WHERE email = '$email'");

// Goed: bindings-array
DB::select('SELECT * FROM users WHERE email = ?', [$email]);

Hoe houd je tabel- en kolomnamen veilig?

Plaatshouders werken alleen voor waarden; structurele delen zoals een tabelnaam, een kolomnaam of een ORDER BY-richting kunnen geen parameters zijn. ORDER BY ? werkt niet. Als een gebruiker kan kiezen op welke kolom wordt gesorteerd, zet de binnenkomende waarde dan niet rechtstreeks in de query — valideer hem tegen een vaste whitelist:

$allowed = ['name', 'created_at', 'price'];
$column = in_array($_GET['sort'], $allowed, true)
    ? $_GET['sort']
    : 'name';

$sql = "SELECT * FROM products ORDER BY $column";

Hier kan de gebruiker slechts een van de vooraf goedgekeurde waarden kiezen; geen enkele andere tekst kan de query bereiken.

Verdediging in lagen

Prepared statements zijn de belangrijkste en voldoende verdediging tegen SQL-injectie. Maar beveiliging is holistisch; deze lagen versmallen het aanvalsoppervlak en beperken mogelijke schade:

  • Principe van minimale rechten: geef de databasegebruiker van de applicatie alleen de rechten die hij nodig heeft. Een webapp hoort geen DROP TABLE- of GRANT-rechten te hebben.
  • Invoervalidatie: dwing het verwachte type en formaat af (een e-mail lijkt op een e-mail, een getal op een getal). Dit alleen voorkomt geen injectie, maar verkleint het aanvalsoppervlak.
  • Verberg foutmeldingen: toon in productie nooit ruwe databasefouten aan de gebruiker; log ze en geef een algemene melding terug. Anders nodig je error-gebaseerde injectie uit.
  • WAF en monitoring: een webapplicatiefirewall kan bekende aanvalspatronen filteren; op zichzelf onvoldoende, maar een extra laag.

Veelgestelde vragen

Voorkomt invoer schonen (sanitizen) SQL-injectie?

Niet betrouwbaar op zichzelf. Aanhalingstekens escapen en blacklistfilters kunnen op verschillende databases en via encodingtrucs worden omzeild. De echte oplossing zijn geparametriseerde query's; schonen is slechts een aanvullende maatregel.

Ik gebruik een ORM — is er nog steeds risico?

Bij standaardquery's beschermt de ORM je omdat hij waarden automatisch bindt. Het risico ontstaat waar je ruwe SQL schrijft (whereRaw, DB::raw, ruwe concatenatie). Gebruik daar altijd bindingsparameters.

Is een stored procedure gebruiken genoeg?

Niet op zichzelf. Als je binnen een stored procedure dynamische SQL met stringconcatenatie opbouwt, verplaatst de kwetsbaarheid zich simpelweg naar de procedure. Je moet dezelfde geparametriseerde aanpak ook binnen procedures volgen.

Laat de beveiliging van je applicatie niet aan het toeval over. Wil je dat ik een bestaande codebase op SQL-injectie nakijk of een veilige datalaag bouw, neem dan contact op en laten we samen een stevig fundament leggen.

Bu kategorideki tüm yazılar →

Devamı için