Blind SQL injection: wat het is en hoe je het voorkomt

Contenttype
Blog

In deze blog kijken we eerst naar de basis, en daarna naar een geavanceerdere variant die vaak over het hoofd wordt gezien: Blind SQL injection.

Blind SQL Injection What It Is And How To Prevent It
Auteur
Christian Peeters

In de OWASP Top 10 staat al jaren “Injection” als een belangrijke securityrisico voor webapplicaties. Er zijn meerdere vormen van injection, maar SQL injection is nog altijd een van de meest gebruikte manieren om een applicatie te compromitteren. Opvallend genoeg is SQL injection relatief eenvoudig te voorkomen, en toch blijft deze kwetsbaarheid steeds terugkomen.

OWASP Top 10

Het Open Web Application Security Project (OWASP) is een non-profitorganisatie die zich richt op het verbeteren van de beveiliging van webapplicaties. Naast diverse open source projecten en conferenties beheert OWASP ook de bekende Top 10.

Deze lijst bevat de belangrijkste securityrisico’s, inclusief voorbeelden en mitigaties, zodat ontwikkelaars deze risico’s kunnen voorkomen. Injection, en specifiek SQL injection, staat al jaren hoog in deze lijst. Iedere ontwikkelaar zou dit risico moeten kennen, maar in de praktijk gaat het nog vaak mis.

Over de meest recente OWASP Top 10 uit 2025 heb ik eerder al een blog geschreven, die je hier kunt lezen.

Daarom eerst een korte introductie van SQL injection, voordat we ingaan op Blind SQL injection.

SQL Injection in het kort

Webapplicaties gebruiken vrijwel altijd een database om data op te slaan en op te halen. De applicatie stuurt queries naar de database, vaak gebaseerd op input van de gebruiker. Denk bijvoorbeeld aan:

  • Het openen van een artikel op een nieuwssite
  • Het zoeken naar producten in een webshop 
  • Het inloggen met een gebruikersnaam en wachtwoord

De query bevat dus vaak gebruikersinput. Bij SQL injection lukt het een aanvaller om die input te manipuleren, zodat er extra SQL-code wordt uitgevoerd.

Neem het volgende voorbeeld:

string query = "SELECT [name] FROM product WHERE Listprice > " + searchPrice + " ORDER BY [name]";

Dit werkt prima als een gebruiker “200” invoert. Maar wat gebeurt er bij:
200 or 1=1

De query wordt dan: 
“SELECT [name] FROM product WHERE Listprice > 200 or 1=1 ORDER BY [name]”

De WHERE clause controleert elk record in de database of deze voldoet aan de vergelijking achter “WHERE”. Aan gezien “1=1” altijd waar is, of te wel altijd voldoet, zullen alle records geretourneerd worden. In dit geval niet zo erg. Alle producten worden getoond. Maar bijvoorbeeld een inlogscherm wordt al problematischer.

Kijk maar eens naar onderstaande query. Wanneer een ontwikkelaar geen rekening houdt met SQL injection, dan zou de code er zo uit kunnen zien.

string query = "SELECT Top(1) * FROM users WHERE username = '" + username + "' and password = '" + password + "'";

 

Door de input af te sluiten met “--” wordt alles wat daarna komt gezien als commentaar en dus genegeerd door de database. Het wachtwoord speelt daardoor geen rol meer.

De “1=1” maakt de voorwaarde altijd waar, waardoor elke gebruiker voldoet. De database pakt vervolgens de eerste gebruiker, en dat is vaak de admin.

Meer voorbeelden:

Met behulp van “UNION” kun je ook gegevens uit andere tabellen ophalen, of informatie over de database zelf opvragen. Denk bijvoorbeeld aan het achterhalen van welke tabellen er in de database aanwezig zijn.

Laten we deze input eens gebruiken in het eerdere voorbeeld, waarin we zoeken naar producten op basis van prijs:

10000 union select [name] from sys.tables --

Met deze input krijg je een overzicht van alle tabellen in de database.

Het uitlezen van data is echter slechts een deel van het probleem. Ook mutaties zoals Updates, Inserts en Deletes zijn mogelijk via SQL injection.

0; update product set [ListPrice] = 0 where [name] = 'Laptop' --

In dit scenario wordt de prijs van een product aangepast naar 0. Vervolgens kun je dus gratis een laptop bestellen en snel de prijs weer terugzetten.

Wat is dan Blind SQL Injection?

Updates, inserts en deletes zijn vervelend wanneer ze onbedoeld worden uitgevoerd. Het grootste risico ontstaat echter wanneer gevoelige data wordt buitgemaakt. Aangepaste of verwijderde data kun je vaak nog herstellen met een backup, maar eenmaal gelekte gegevens zijn niet meer terug te halen.

Daarom ligt de focus bij SQL injection vaak op SELECT-queries, omdat die direct data kunnen uitlezen. UPDATE-statements passen alleen gegevens aan en geven geen data terug in de response van de applicatie.

Op het eerste gezicht lijkt het dus alsof een injectie in een update-statement geen bruikbare informatie oplevert voor een aanvaller.

In de praktijk ligt dat anders. Ook zonder directe output is het mogelijk om informatie uit de database te halen. Dit noemen we Blind SQL injection.

Ondanks dat een hacker geen directe output ziet van de database (vandaar “blind”), kan hij toch informatie achterhalen. Dit gebeurt vaak door het gedrag van de applicatie te beïnvloeden, bijvoorbeeld via de responsetijd.

Als voorbeeld nemen we een update-statement:

UPDATE Product SET name = name WHERE ProductID = 1

Waarbij “1” afhankelijk is van gebruikersinput.

Een hacker kan dan in plaats van “1” ook het volgende injecteren

1; IF (SELECT 1 FROM Person WHERE Lastname = 'Peeters' AND SUBSTRING(FirstName, 1, 1) = 'A') = 1 WAITFOR DELAY '0:0:5'

Als de eerste letter van de voornaam een “A” is, wordt de response met 5 seconden vertraagd. Blijft de response snel, dan klopt de voorwaarde niet. 

In dit geval is te zien dat de query vrijwel direct klaar is (minder dan een seconde), dus de eerste letter is geen “A”. Door dit proces te herhalen en telkens een andere letter te testen, kun je stap voor stap de juiste waarde achterhalen. In de praktijk wordt dit meestal geautomatiseerd.

Als je dit handmatig zou doen, merk je al snel dat bijvoorbeeld de letter “C” wél een vertraging van 5 seconden veroorzaakt. Daarmee weet je dat dit de juiste letter is.

Vervolgens herhaal je dit proces voor de volgende letter!

Het is een bewerkelijke aanpak, maar zeker niet onmogelijk om zo data uit een database te verkrijgen.

Blind SQL injection kent meerdere varianten, maar de kern blijft hetzelfde: je kunt geen data direct uitlezen, maar leidt informatie af via ja/nee-vragen (true/false).

Naast tijdgebaseerde aanvallen bestaan er ook varianten die gebruikmaken van foutmeldingen. Als een applicatie bijvoorbeeld een foutpagina toont bij databasefouten, kan een aanvaller bewust fouten veroorzaken om zo informatie af te leiden.

Met deze technieken kun je bijvoorbeeld achterhalen:

  • Is er een tabel “users”
  • Is er een kolom “username”
  • Is er een gebruiker “admin”
  • Is er een kolom “creditcard”
  • Enzovoort

Dit voorbeeld is uitgevoerd met Microsoft SQL Server, maar ook met andere databases is dit mogelijk. Zo heeft MySQL een Sleep() functie en PostGreSQL een pg_sleep().

Hoe voorkom je SQL injection?

De belangrijkste vraag. Hoe voorkom je SQL Injection?

De basisregel is simpel: vertrouw nooit input van de gebruiker. Niet alleen bij SQL-queries, maar in elke situatie waarin input vanaf de client wordt gebruikt. Denk daarbij niet alleen aan invoervelden op een pagina, maar ook aan URL-parameters, API-calls en JSON payloads.

Wat zou er bijvoorbeeld gebeuren als ik deze URL aan zou roepen?
http://example.com/app/accountView?id=' or '1'=‘1

Validatie kan helpen, maar heeft zijn beperkingen. Client-side validatie is vooral bedoeld voor gebruiksgemak en biedt geen echte beveiliging, omdat deze eenvoudig te omzeilen is. Server-side validatie is noodzakelijk, maar ook daar zijn vaak manieren om omheen te werken, bijvoorbeeld door input te encoderen zodat deze niet wordt herkend.

De beste oplossing is daarom gebruikmaken van parameters.

Bij parameterized queries bouw je de SQL instructie op aan de hand van de query tekst waarin parameters zijn opgenomen. Vervolgens worden de waardes voor de parameters gevuld. Het framework zorgt dan voor de juiste afhandeling van de query en voorkomt de uitvoer van geïnjecteerde opdrachten.

string sqlstr = "SELECT [name] FROM product where Listprice > @price ORDER BY [name]";
SqlCommand comm = new SqlCommand(sqlstr, conn);
SqlParameter priceParam = new SqlParameter("price", SqlDbType.Money);
priceParam.Value = searchPrice;
comm.Parameters.Add(priceParam);

Bovenstaand voorbeeld is geschreven in C#, maar vergelijkbare methoden zijn beschikbaar in andere talen, zoals PHP.

In dit voorbeeld wordt de gebruikersinput niet meer direct in de query geplaatst, maar gebruikt via de parameter @price. In de voorlaatste regel wordt de waarde van deze parameter gevuld met de inhoud van de variabele searchPrice, die de gebruikersinput bevat.

Vervolgens wordt de parameter aan het commando gekoppeld, zodat de query veilig kan worden uitgevoerd zonder dat geïnjecteerde SQL-code wordt uitgevoerd. Entity Framework dwingt af om op een veilige manier te werken, maar het is mogelijk om daaromheen te werken. Dan moet je dus ook altijd parameters toepassen om te voorkomen dat je een kwetsbaarheid introduceert.

Meer weten?

Bij Betabit bouwen en beheren we bedrijfskritische software, waarbij security vanaf het allereerste begin is geïntegreerd. We maken gebruik van beproefde aanpakken zoals threat modeling en Privacy by Design om risico’s vroegtijdig te identificeren en te beheersen.

Deze kennis delen we ook via onze trainingen. We bieden verschillende trainingen op het gebied van security en softwareontwikkeling om jouw team te versterken.

Wil je meer weten over SQL-injectie of het beveiligen van webapplicaties, neem dan gerust contact met ons op of bespreek jouw specifieke situatie.

Meer blogposts

Altijd op de hoogte met onze tech-updates!

Schrijf je in en ontvang om de week een update met de nieuwste kennis en ontwikkelingen.