SQL injection voorkomen WordPress begint bij één inzicht: de aanval komt vrijwel altijd binnen via verouderde plugins en slecht geschreven custom code. SQL injection staat al meer dan tien jaar in de OWASP Top 10 en het is er nog steeds. Volgens OWASP hoort injection bij de meest voorkomende kwetsbaarheden op het web. Op WordPress komt het vooral binnen via verouderde plugins en slecht geschreven custom code.
Dit artikel laat zien hoe je SQL injection voorkomt in WordPress. Niet alleen de algemene tips, maar concreet: welk patroon je in PHP gebruikt, welke functies sanitiseren, welke plugins het fout doen en welke WAF-regels het opvangen. Code-voorbeelden inbegrepen.
Ik ben technisch implementeerder, geen SEO-strateeg. Wat hieronder staat, gebruik ik dagelijks bij hardening na een incident en bij onderhoud op bestaande sites.

Hoe SQL injection vandaag op WordPress werkt
Een SQL injection-aanval slaat code binnen via een invoerveld of URL-parameter. Die code wordt door de database uitgevoerd als ware het een gewone query. Het klassieke voorbeeld is een formulierveld met ' OR '1'='1 dat de hele gebruikerstabel teruggeeft.
In WordPress zelf is de core relatief goed beschermd. De $wpdb-klasse biedt veilige functies voor database-queries. Maar de meeste sites draaien tientallen plugins en thema’s. Daar zit de zwakke plek.
Wat ik in de praktijk zie als hoofdoorzaken:
- Verouderde plugins die nog werken met oude database-functies
- Custom code in een thema dat door een vorige bouwer is achtergelaten
- Formulieren of zoekfunctionaliteit die direct queries opbouwt
- Imports of API-koppelingen die externe data zonder controle doorzetten
Het patroon is bijna altijd hetzelfde: ergens komt input binnen die zonder bewerking in een query belandt. Het doel van hardening is om dat patroon overal te doorbreken.
Het $wpdb->prepare()-patroon
De basis voor veilige queries in WordPress heet prepare(). Het is een functie van de $wpdb-klasse die placeholders gebruikt in plaats van directe stringconcatenatie. Officiële documentatie staat in de WordPress developer reference.
Een onveilige query ziet er zo uit:
php
$user_id = $_GET['user_id'];
$result = $wpdb->get_row("SELECT * FROM wp_users WHERE ID = $user_id");Dit is direct kwetsbaar. Iemand kan 1; DROP TABLE wp_users meesturen en de tabel verdwijnt.
De veilige variant gebruikt prepare():
php
$user_id = absint($_GET['user_id']);
$result = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM wp_users WHERE ID = %d",
$user_id
)
);Twee dingen gebeuren hier. Eerst dwingt absint() de input naar een positief geheel getal. Daarna zorgt %d ervoor dat de waarde als integer in de query terechtkomt, escaped en al.
De placeholders zijn:
%dvoor integers%fvoor floats%svoor strings (automatisch quoted en escaped)%ivoor identifiers zoals tabel- en kolomnamen (sinds WordPress 6.2)
Schrijf je een query met $wpdb? Dan moet er altijd een prepare() omheen, ook als de input “van een veilige bron” lijkt. Vertrouw geen input, ook niet je eigen.
SQL injection simulatie
Zie het verschil: kwetsbare vs veilige query
Kies een invoer en voer hem “uit”. Links een query met concatenatie, rechts dezelfde met $wpdb->prepare(). Dit is een veilige simulatie, er draait geen echte database.
Kwetsbaar (concatenatie)
Kwetsbaar
$sql = "SELECT * FROM wp_users WHERE user_login = 'Jan'";
Resultaat
Veilig (prepare)
Veilig
$sql = $wpdb->prepare( "SELECT * FROM wp_users WHERE user_login = %s", "Jan" );
Resultaat
De veilige query behandelt invoer altijd als waarde, nooit als code. Dat is precies wat $wpdb->prepare() doet.
Interactieve simulatie, zet JavaScript aan.
$sql = "SELECT * FROM wp_users WHERE user_login = '" . $_GET['u'] . "'";
$sql = $wpdb->prepare( "SELECT * FROM wp_users WHERE user_login = %s", $_GET['u'] );De veilige query behandelt invoer altijd als waarde, nooit als code. Dat is precies wat $wpdb->prepare() doet.
Input sanitization op de juiste laag
Naast prepare() is sanitization van input een tweede verdedigingslaag. WordPress heeft een lijst sanitization-functies die je per veldtype toepast.
De meest gebruikte:
sanitize_text_field()voor losse tekstveldensanitize_email()voor e-mailadressensanitize_key()voor slugs en interne keysabsint()voor positieve integersintval()voor algemene integersesc_url_raw()voor URL’s die in de database komenwp_kses_post()voor HTML-content waar wel tags in mogen
Belangrijk: sanitize op input, escape op output. Dat zijn twee verschillende stappen. Sanitization gaat over wat je opslaat. Escaping (esc_html, esc_attr, esc_url) gaat over hoe je het laat zien.
Voor formulieren die je zelf bouwt, ziet het er ongeveer zo uit:
php
$email = sanitize_email( $_POST['email'] ?? '' );
$name = sanitize_text_field( $_POST['name'] ?? '' );
if ( ! is_email( $email ) ) {
wp_die( 'Ongeldig e-mailadres' );
}Pas hierna komt het opslaan via $wpdb->insert(), dat zelf al placeholders gebruikt.

Plugins die het fout doen
In mijn ervaring zijn drie soorten plugins de meest voorkomende bron van SQL injection-kwetsbaarheden op WordPress.
Oude formulier-plugins. Vooral plugins die jaren niet zijn bijgewerkt. Ze bouwen queries vaak met stringconcatenatie. Bij een audit op een gehackte site is dit doorgaans de eerste plek waar ik kijk.
Custom plugins van vorige bouwers. Een ontwikkelaar bouwt een snelle koppeling met een externe API. Werkt prima, gaat live, niemand kijkt er meer naar. Twee jaar later zit er een lek in. Custom plugins krijgen geen automatische security-fixes.
Plugins met “premium” patches via nulled-sites. Gepirateerde versies van betaalde plugins. Die zijn vaak getampered met. Lypii-beleid is duidelijk: nooit gebruiken, nooit aanraden. Wie een nulled-plugin draait, draait een blackbox.
Hoe vind je verdachte plugins? Een aantal stappen:
- Controleer welke plugins meer dan zes maanden geen update kregen
- Check elke plugin tegen de Patchstack-database op bekende kwetsbaarheden
- Verwijder plugins die je niet actief gebruikt
- Bij twijfel: deactiveer en kijk of de site nog werkt
Op een lopend onderhoudscontract loopt dit elke maand mee. Dat is de reden waarom een WordPress onderhoudscontract goedkoper uitkomt dan reageren na een hack.
.htaccess-regels die helpen
Apache- en LiteSpeed-servers met een .htaccess-bestand kunnen een aantal generieke injection-pogingen al op de webserver tegenhouden. Dit vervangt geen veilige code, het is een extra filter aan de poort.
Een voorbeeld van regels die ik gebruik:
apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (union|select|insert|drop|update|md5|benchmark) [NC,OR]
RewriteCond %{QUERY_STRING} (concat|sleep|delete|truncate) [NC,OR]
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC]
RewriteRule .* - [F,L]
</IfModule>Dit blokkeert URL’s met verdachte SQL-keywords in de query string. Pas op: te agressieve regels kunnen legitieme functionaliteit breken. Test dit altijd op staging voor het live gaat. Voor Nginx draait de logica via de server-config, niet via .htaccess.
Voor wp-admin zelf kan je een extra IP-restrictie toevoegen, of basic auth voor wp-login.php. Niet specifiek voor SQL injection, wel een algemene hardening-stap die scriptkiddies stopt.
WAF: Wordfence, Sucuri en Cloudflare
Een Web Application Firewall vangt veel injection-pogingen op voordat ze de site bereiken. Drie opties die ik in de praktijk inzet.
Wordfence. Plugin-based WAF die binnen WordPress draait. Werkt prima voor kleinere sites. De gratis versie heeft een vertraging op nieuwe signatures, premium krijgt ze direct. Punt van aandacht: Wordfence draait pas nadat WordPress is opgestart, dus zware botaanvallen kosten alsnog server-resources.
Sucuri. Cloud-based WAF die voor de site staat. Het verkeer loopt eerst via Sucuri, dan pas naar je server. Beter tegen DDoS en zware aanvallen, duurder per maand. Goed voor webshops en sites met veel verkeer.
Cloudflare. Combinatie van CDN en WAF. De gratis versie heeft beperkte WAF-regels, de Pro-versie en hoger biedt OWASP-rule sets. Voor MKB-sites vaak een goede balans tussen prijs en bescherming. Documentatie staat in de Cloudflare WAF-docs.
Geen van deze WAF’s vervangt veilige code. Ze vangen het meest voorkomende verkeer op, maar een doelgerichte aanval op een specifieke plugin-kwetsbaarheid komt soms toch door. Defense in depth: meerdere lagen die elkaar opvangen.

SQL injection voorkomen WordPress: praktische volgorde
Werkt het allemaal samen? Ja, maar niet alles tegelijk. Een typische hardening-volgorde op een bestaande site:
- Eerst audit van plugins en thema’s, verouderde en ongebruikte exemplaren verwijderen
- Updates van core, plugins en thema’s op staging testen, daarna doorvoeren
- Custom code in het thema en eventuele custom plugins doorlopen op
$wpdb-gebruik - WAF activeren of upgraden (Wordfence, Sucuri of Cloudflare)
.htaccess– of Nginx-regels toevoegen voor generieke query-string filtering- Logs nakijken op verdachte patronen van de afgelopen 30 dagen
- Back-up- en restore-test draaien
Voor een gemiddelde mkb-site kost dit één tot drie uur, afhankelijk van de hoeveelheid custom code. Voor een gehackte WordPress-site herstellen is dit een vast onderdeel van de cleanup. Geen herstel zonder hardening, anders ben je er volgende maand weer.
Wat ik niet doe
Eerlijk benoemen wat er buiten scope valt. Juridisch advies bij een datalek hoort bij een privacy-advocaat, niet bij een webmaster. Pen-tests op enterprise-niveau zijn werk voor gespecialiseerde security-bureaus.
Wat ik wel doe: praktische hardening voor MKB-sites die normaal in productie draaien. WordPress, WooCommerce, en ook Joomla, Drupal of Laravel als dat in je stack zit. De principes (prepared statements, sanitization, WAF) gelden buiten WordPress net zo goed.
Geen 24/7-support. ’s Nachts ben ik niet beschikbaar, behalve bij afgesproken spoed. Spoedwerk reken ik tegen €105 per uur. Standaard uurtarief is €70. Voor lopend onderhoud zijn er twee abonnementen: Onderhoud Basis op €58 per maand en Onderhoud Plus op €175 per maand.
Heb je een concrete vraag of een vermoeden van een kwetsbaarheid? Stuur de URL en kort wat je ziet. Ik reageer binnen 4 uur op werkdagen. Meer artikelen staan in de categorie website beveiliging.



