Bijna alle dynamische sites, WordPress inbegrepen, bewaren hun inhoud in een database die met SQL wordt bevraagd. Als de code een query bouwt door er rechtstreeks een waarde van de bezoeker in te plakken, vermengt hij twee dingen die gescheiden horen te blijven: instructies en gegevens. De aanvaller maakt daar gebruik van door instructies te schrijven waar gegevens hadden moeten staan. MITRE registreert deze zwakte als CWE-89.
Hoe een SQL-injectie werkt
Neem code die een artikel op naam zoekt en de query met de hand in elkaar zet:
$sql = "SELECT * FROM artikelen WHERE naam = '" . $_GET['naam'] . "'";
Stuurt de bezoeker x' OR 'a'='a, dan sluit het aanhalingsteken de tekenreeks af die de ontwikkelaar had bedoeld, en wordt de rest SQL. De voorwaarde is altijd waar: de database geeft de hele tabel terug. Met andere payloads kan de aanvaller gegevens uit andere tabellen halen, ze wijzigen of verwijderen.

Wat een aanvaller ermee kan bereiken
OWASP vat de mogelijke gevolgen van een geslaagde injectie samen:
- gevoelige gegevens lezen: e-mailadressen, bestellingen, wachtwoordhashes;
- records toevoegen, wijzigen of verwijderen, bijvoorbeeld een account de rol Beheerder geven, wat er een vorm van privilege-escalatie van maakt;
- een inlogcontrole omzeilen;
- in sommige gevallen bestanden op de server lezen of opdrachten naar het systeem sturen, tot en met remote code execution.
Niet elke injectie toont het resultaat op het scherm. Bij de zogenaamde blinde varianten leidt de aanvaller de gegevens een voor een af uit verschillen in het antwoord of uit vertragingen. Dat gaat trager, maar met een automatisch hulpmiddel net zo effectief. Tussen de lekken die sinds 2020 in WooCommerce zijn gevonden zitten meerdere SQL-injecties, en de waarschuwing over All-in-One WP Migration laat zien dat zo'n lek al wordt afgetast zodra het bekend wordt.
Voor WordPress: $wpdb->prepare()
De functies op hoog niveau (WP_Query, get_posts(), update_option()...) bouwen hun queries zelf. Zodra een plugin een eigen query schrijft met het object $wpdb, moet elke variabele waarde door $wpdb->prepare(). De placeholders zijn %s (tekenreeks), %d (geheel getal), %f (decimaal getal) en, sinds WordPress 6.2, %i voor de naam van een tabel of kolom.
$ids = $wpdb->get_col( $wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
$auteur_id,
'publish'
) );
- Zet geen aanhalingstekens rond
%s: de functie voegt ze zelf toe. - Geef bij een zoekopdracht met
LIKEde volledige tekenreeks als argument mee, nadat je hem met$wpdb->esc_like()hebt behandeld. - ID's omzetten met
absint()voordat je ze gebruikt is een extra zekerheid, geen vervanging van het prepared statement.
Hoe je je als eigenaar beschermt
- Installeer beveiligingsupdates van plugins snel: hun code repareer je niet zelf.
- Geef de databasegebruiker alleen de rechten die WordPress nodig heeft, zodat een gekaapte query minder schade aanricht.
- Zet een web application firewall voor de site: de regels herkennen de bekendste injectie-payloads en kunnen een lek afdekken tot de patch er is.
WP Masquerade detecteert en blokkeert geen SQL-injecties: het is geen web application firewall. Door WordPress te verbergen wordt het lastiger om te zien welke plugins geïnstalleerd zijn, wat sommige bots hindert, maar dat vervangt nooit de update van de kwetsbare plugin.
Veelgemaakte fouten
- "Met de hand" escapen met
addslashes()of het vervangen van tekens, in plaats van een prepared statement te gebruiken. - De waarden wel voorbereiden, maar een kolomnaam of sorteervolgorde uit het verzoek ongewijzigd invoegen: gebruik
%iof een whitelist. - Denken dat gegevens die veilig in de database staan overal veilig zijn: bij de weergave moet je met escaping cross-site scripting voorkomen.