SQL-injectie in Magento-modules: herkennen en voorkomen
Terug naar blog

SQL-injectie in Magento-modules: herkennen en voorkomen

Bijgewerkt 7 min leestijd

Eén regel met een variabele midden in een SQL-string kan je hele klantendatabase openzetten. In oude Magento-modules vinden we die regels nog steeds.

Eén regel met een variabele midden in een SQL-string kan je hele klantendatabase openzetten. In oude Magento-modules vinden we die regels nog steeds.

De core van Magento is goed beschermd tegen SQL-injectie. Het gevaar zit in custom modules, oudere extensies en snelle "even een query"-oplossingen. In dit artikel laten we zien hoe het misgaat, hoe het veilig hoort en hoe wij het in een audit opsporen.

Wat SQL-injectie is

SQL-injectie ontstaat als invoer van een gebruiker onderdeel wordt van een SQL-statement zonder dat de database het verschil kent tussen data en commando. Een aanvaller kan dan:

  • Gegevens lezen die hij niet mag zien, zoals klantgegevens of admin-hashes.
  • Gegevens wijzigen of verwijderen.
  • In sommige configuraties verder doordringen in de server.

In de OWASP Top 10:2025 valt dit onder A05 Injection. Zie OWASP en je webshop.

Veilig werken: de volgorde van voorkeur

Magento biedt meerdere lagen. Gebruik ze in deze volgorde:

  1. Repository en SearchCriteria. Voor het ophalen van entiteiten het veiligst en meest upgrade-vast.
  2. Collections met addFieldToFilter. De collection regelt de binding.
  3. De Zend_Db/Magento select-API met bindings. Voor lagere query's, met quoteInto of bind-parameters.
  4. Raw SQL. Alleen als het echt niet anders kan, altijd met bindings.

SearchCriteria en collections

Een filter via de service-laag:

php
$searchCriteria = $this->searchCriteriaBuilder
    ->addFilter('sku', $sku)
    ->create();
$products = $this->productRepository->getList($searchCriteria);

Hier komt $sku nooit als tekst in de SQL terecht. Een collection werkt vergelijkbaar:

php
$collection->addFieldToFilter('status', ['eq' => $status]);

De waarde wordt gebonden of correct gequote. Dit is de manier die we in nieuwe code verwachten.

Bindings en quoteInto

Als je een directe query nodig hebt, gebruik je de database-adapter met bind-parameters:

php
$connection = $this->resource->getConnection();
$select = $connection->select()
    ->from($this->resource->getTableName('mijn_tabel'))
    ->where('email = ?', $email);
$rows = $connection->fetchAll($select);

Het vraagteken wordt vervangen door een correct gequote waarde. Of met expliciete bindings:

php
$sql = 'SELECT * FROM ' . $table . ' WHERE email = :email';
$rows = $connection->fetchAll($sql, ['email' => $email]);

En voor losse stukken: $connection->quoteInto('email = ?', $email).

Gevaarlijke patronen

Dit is wat we in audits tegenkomen. Het ziet er onschuldig uit:

php
// Gevaarlijk
$sql = "SELECT * FROM mijn_tabel WHERE email = '" . $email . "'";
$connection->query($sql);

// Gevaarlijk
$select->where("status = " . $status);

// Gevaarlijk
$connection->query("DELETE FROM mijn_tabel WHERE id IN (" . implode(',', $ids) . ")");

In alle drie gaat ongevalideerde invoer rechtstreeks in de query. Zelfs als een waarde "alleen een getal" hoort te zijn, moet je het afdwingen met een cast of validatie en liever nog binden.

Sortering en kolomnamen

Hier gaan ervaren developers ook de fout in. Bindings werken voor waarden, niet voor kolomnamen of sorteerrichting. Een parameter als ?sort=price&dir=desc die je direct in ORDER BY zet, is een klassiek lek:

php
// Gevaarlijk
$select->order($request->getParam('sort') . ' ' . $request->getParam('dir'));

De oplossing is een allowlist:

php
$toegestaan = ['price', 'name', 'created_at'];
$sort = in_array($request->getParam('sort'), $toegestaan, true)
    ? $request->getParam('sort')
    : 'created_at';
$dir = strtoupper($request->getParam('dir')) === 'ASC' ? 'ASC' : 'DESC';

Hetzelfde geldt voor tabelnamen, LIMIT-waarden en filteroperatoren. Een allowlist is het enige echte antwoord.

LIKE en IN

LIKE-zoekopdrachten vragen extra aandacht, omdat % en _ speciale tekens zijn. Binding beschermt tegen injectie, maar niet tegen een gebruiker die met wildcards je database zwaar belast. Escape daarom wildcards waar dat nodig is.

Bij IN-lijsten: bouw geen komma-gescheiden string met implode van ruwe invoer. Gebruik de ingebouwde IN-ondersteuning:

php
$select->where('id IN (?)', $ids);

Verstopte bronnen van invoer

Niet alle invoer komt uit een formulier. Denk ook aan:

  • Request headers en cookies.
  • Productattributen of CSV-importen die later in een query worden gebruikt.
  • Data uit de database die eerder door een gebruiker is ingevoerd, en nu in een tweede query wordt gebruikt (second-order injection).
  • Parameters van API-aanroepen. Zie API-beveiliging voor e-commerce.

Waarom oude modules vaker lekken

Oudere extensies zijn vaak geschreven voor Magento 1 of voor het begin van Magento 2, voordat bindings gemeengoed waren. Typische kenmerken:

  • Directe SQL in controllers of blocks, soms zelfs in een .phtml.
  • Gebruik van ObjectManager om een connectie te pakken.
  • Geen validatie op request-parameters.
  • Een leverancier die niet meer bestaat.

Dit hoort bij het bredere onderwerp extensies en supply chain-risico. Een extensie zonder updates is een kwetsbaarheid die wacht. Overweeg vervanging of zelf onderhouden.

Hoe wij het opsporen in een audit

In een Magento security audit pakken we SQL-injectie systematisch aan:

  1. Statisch zoeken. We scannen op patronen: ->query(, ->fetchAll(, ->where( met stringconcatenatie, ORDER BY met variabelen, en implode in queries. Een gewone grep brengt al veel boven water:
bash
grep -rnE "(query|fetchAll|fetchRow|fetchOne|where)\(.*\\quot; app/code/ --include=*.php
  1. Invoer volgen. Voor elke gevonden query: waar komt de waarde vandaan? Is het een request-parameter, header of database-veld?
  2. Handmatig testen op een testomgeving met een kopie van de data. Nooit op productie.
  3. Statische analyse in CI. PHPStan, Psalm en Semgrep met regels voor SQL-concatenatie vangen regressies. Zie ook veilig bouwen met een klein team.
  4. Databaserechten nakijken. De database-gebruiker van Magento hoeft geen rechten buiten de eigen database. Beperkte rechten verkleinen de schade als het toch misgaat.

Dit is gericht op bevindingen die je kunt oplossen, geen lange lijst met theoretische punten. Het bredere beeld vind je in wat een Magento security audit controleert.

Voorkomen in je eigen proces

  • Regel in je coding standards: geen stringconcatenatie in queries, ook niet "even snel".
  • Review elke query. Een pull request met raw SQL krijgt altijd een tweede paar ogen.
  • Gebruik de service-laag waar mogelijk en raw SQL alleen voor rapportage of bulkwerk.
  • Allowlists voor alles wat geen waarde is: kolommen, richtingen, tabellen.
  • Minimale databaserechten per applicatie.
  • Foutmeldingen niet tonen aan bezoekers. SQL-fouten in de browser helpen aanvallers enorm.

Wanneer is dit overkill?

Gebruik je alleen de standaard Magento en een paar goed onderhouden extensies, dan is dit risico klein. De moeite loont bij shops met veel maatwerk, oude modules, eigen koppelingen of een eigen zoek- en filterlaag. Een gerichte code-scan van de eigen modules kost dan weinig tijd en levert vaak direct resultaat.

Een voorbeeld van een veilige herschrijving

Neem een oude module die klanten zoekt op naam met een sorteeroptie. De onveilige versie:

php
$name = $this->getRequest()->getParam('name');
$sort = $this->getRequest()->getParam('sort');
$sql = "SELECT * FROM mijn_klanten WHERE name LIKE '%" . $name . "%' ORDER BY " . $sort;
$rows = $connection->fetchAll($sql);

Er zijn twee lekken: de zoekterm en de sortering. De veilige versie:

php
$name = (string) $this->getRequest()->getParam('name');
$toegestaan = ['name', 'created_at'];
$sort = in_array($this->getRequest()->getParam('sort'), $toegestaan, true)
    ? $this->getRequest()->getParam('sort')
    : 'created_at';

$select = $connection->select()
    ->from($this->resource->getTableName('mijn_klanten'))
    ->where('name LIKE ?', '%' . $name . '%')
    ->order($sort . ' ASC');
$rows = $connection->fetchAll($select);

De waarde gaat via een binding, de kolom komt uit een allowlist. Het is niet meer code, alleen bewuster geschreven.

Wat je ziet als er misbruik is

SQL-injectie laat sporen na, als je weet waar je moet kijken:

  • Ongewoon lange of vreemde waarden in de toegangslogs van endpoints met zoek- of filterparameters.
  • Veel foutmeldingen van de database in een korte periode.
  • Een plotselinge piek in zware queries zonder verklaring in je verkeer.
  • Nieuwe of gewijzigde admin-gebruikers in de database zonder dat iemand ze heeft aangemaakt.

Heb je zo'n vermoeden, ga dan door naar Magento 2 gehackt: wat nu?. Maak eerst een kopie van logs en database voordat je opruimt.

Wat een WAF wel en niet doet

Een Web Application Firewall herkent veel gangbare injectiepatronen en blokkeert ze. Dat is waardevol als extra laag. Het heeft grenzen:

  • Aanvallers kunnen payloads aanpassen om regels te ontwijken.
  • Een WAF kent je applicatie niet. Een legitiem verzoek dat toevallig op een aanval lijkt, wordt soms geblokkeerd.
  • Het vervangt geen reparatie van de code eronder.

Gebruik een WAF om tijd te winnen en ruis weg te filteren, en repareer de bron.

Veelgestelde vragen

Is Magento zelf kwetsbaar voor SQL-injectie?

De core is goed beschermd, maar er zijn in het verleden wel SQL-injectiekwetsbaarheden gevonden en gepatcht. Daarom blijft patchen belangrijk. Het grootste risico zit in custom modules en extensies.

Beschermt addFieldToFilter tegen SQL-injectie?

Ja, voor de waarde. De collection bindt of quote de waarde. De veldnaam moet je zelf in de hand houden en nooit rechtstreeks uit gebruikersinvoer halen.

Kan ik ORDER BY veilig maken met bindings?

Nee. Bindings werken voor waarden, niet voor kolomnamen of sorteerrichting. Gebruik een allowlist van toegestane waarden.

Hoe vind ik SQL-injectie in een bestaande shop?

Zoek in je eigen modules naar queries met stringconcatenatie, volg waar de variabelen vandaan komen en test op een kopie van de omgeving. Een security audit doet dit systematisch, inclusief extensies.

Is een WAF genoeg als bescherming?

Een WAF vangt veel standaardpogingen af, maar is geen vervanging voor veilige code. Aanvallers omzeilen WAF-regels regelmatig. Zie WAF en bot-bescherming als aanvulling.

Wil je dat wij je custom modules doorlichten op SQL-injectie en andere kwetsbaarheden? Bekijk onze Magento security-aanpak of neem contact op via /contact.

Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check