BLOG

Softwareontwikkeling

Wat is “Blind SQL injection” en wat doe je ertegen?

21 december 2020

In de OWASP top 10 staat sinds jaar en dag “Injection” op de nummer 1 als belangrijkste beveiligingsrisico voor een webapplicatie. Nu zijn er meerdere vormen van Injection, maar SQL injection is zeker 1 van de meest gebruikte manieren om een webapplicatie te hacken. SQL injection is relatief eenvoudig te voorkomen, maar toch blijft het de nummer 1. In deze blog kijken we naar de basis, maar ook naar een geavanceerdere vorm van SQL injection die vaker over het hoofd wordt gezien: Blind SQL injection.

OWASP Top 10

Het Open Web Application Security Project (OWASP) is een non-profit stichting die zich richt op het verbeteren van de beveiliging van webapplicaties. Naast veel open source projecten en conferenties, beheert de stichting ook de OWASP Top 10. In deze lijst worden de belangrijkste security risks bijgehouden en voorzien van voorbeelden en oplossingen zodat ontwikkelaars deze risico’s kunnen afdichten in hun software. Injection en dan met name SQL injection is de nummer 1. Elke ontwikkelaar zou dit beveiligingsrisico moeten kennen en voorkomen, maar blijkbaar gaat het toch nog vaak fout. Daarom kort een introductie in wat het eigenlijk is alvorens we naar Blind SQL Injection gaan kijken.

SQL Injection in het kort

Een webapplicatie zal vaak een database gebruiken om data op te slaan en uit te lezen. Er zullen dus queries van de applicatie naar de database worden gestuurd. De opgevraagde informatie is over het algemeen ook afhankelijk van de input van de gebruiker. Bijvoorbeeld:

  • Op een nieuwssite klik je op een artikel, dan wordt dit artikel opgehaald uit de database
  • Op een webshop zoek je op een bepaalde naam, dan wordt de catalogus doorzocht op die input
  • Als je inlogt op een website, dan worden jouw gebruikersnaam en wachtwoord gecontroleerd met gegevens uit de database.

De query richting de database zal dus over het algemeen input van de gebruiker bevatten. Bij SQL injection lukt het de hacker om deze input te voorzien van andere SQL opdrachten, zodat de originele query geïnjecteerd wordt met extra SQL opdrachten.

Laten we als voorbeeld een website nemen waar je kunt zoeken op een bepaalde prijs. De variabele “searchPrice” is gevuld met input van gebruiker (de prijs waar hij op wil zoeken). Applicatiecode kan dan de query als volgt opbouwen:

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

Dat is geen probleem als de gebruiker netjes “200” invult, maar wat als je het volgende in zou vullen: “200 or 1=1“. De query die dan richting database wordt gestuurd is dan dus:

“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”. Aangezien “1=1” altijd waar is, oftewel altijd voldoet, zullen alle records geretourneerd worden. In dit geval is dat niet zo erg; alle producten worden getoond. Echter op bijvoorbeeld een inlogscherm wordt dit al problematischer.

Fout geschreven code zou er als volgt uit kunnen zien:

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

Door de input te eindigen met “- -” wordt alles achter de input door de database gezien als commentaar, of te wel, niet meer uitgevoerd. De “1=1” zorgt ervoor dat alle records voldoen, dus zal nu de eerste gebruiker uit de database gebruikt worden om in te loggen. Vaak is dat ook de Admin, dus dat is mooi meegenomen.

Nog even wat voorbeeldjes:
Je kunt met “UNION” natuurlijk ook informatie uit andere tabellen halen of informatie over de database zelf uitvragen. Bijvoorbeeld welke tabellen bestaan er eigenlijk in de database. Laten we de volgende input eens invoeren in het eerste voorbeeld waarbij op producten met hun prijs kunnen zoeken: “10000 union select [name] from sys.tables – -”

 

Dan krijg je dus alle tabellen uit de database te zien.
Overigens is het ongeoorloofd uitlezen van gegevens uit de database slechts een deel van het probleem. Updates, Inserts en Deletes werken net zo goed, bijvoorbeeld:
“0; update product set [ListPrice] = 0 where [name] = ‘Laptop’ – -”

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 natuurlijk vervelend wanneer ze ongewild worden uitgevoerd. Echter het grootste probleem is over het algemeen wanneer gegevens op straat komen te liggen. Het aanpassen of verwijderen van data kun je herstellen door Backups te restoren, maar gegevens op straat… dan valt er weinig meer te herstellen.

Daarom ligt de focus bij de beveiliging van SQL Injection vaak op de SELECT queries. Daar kun je namelijk extra gegevens proberen uit te lezen. UPDATE statements passen gegevens aan, maar halen geen gegevens op en tonen deze dus ook niet op de website. Wanneer de hacker weet te injecteren bij een update statement, dan krijgt hij dus geen informatie uit de database…. Toch? Toch?
Dat kan toch wel: Blind SQL injection!

Ondanks dat de hacker geen zicht heeft op de informatie die uit de database komt (blind), weet hij wel informatie te vergaren. Dit werkt over het algemeen door de responstijd te laten veranderen. Als voorbeeld gebruik ik een update statement.

UPDATE Product SET name = name WHERE ProductID = 1

Waarbij “1” afhankelijk is van gebruikers input.

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'

Wanneer de eerste letter van de voornaam inderdaad “A” is, dan duurt het 5 seconden langer voordat er een respons komt.

Echter is rechts onderin te zien dat de query nagenoeg direct klaar was (minder dan 1 seconde), dus de eerste letter is geen “A”. Je zult dit normaal gesproken gaan automatiseren, maar als je 1 voor 1 de mogelijkhedet gaan uitvoeren kom je er al snel achter dat de letter “C” 5 seconden langer duurt.

Op naar de volgende letter! Het is een stuk bewerkelijker, maar zeker niet onmogelijk om op deze manier data uit een database te verkrijgen.

Zo kent Blind SQL injection meerdere vormen. Maar in de basis betekent Blind SQL injection dat je niet direct gegevens uit kunt lezen, maar met JA/NEE (true/false) vragen data analyseert.

Naast de tijd-gebaseerde mogelijkheid, kan je bijvoorbeeld ook kijken naar foutmeldingen die getoond worden. Wellicht wordt er een foutpagina getoond wanneer de database een fout heeft. Dan kun je met een zelfde true/false vraag ook zorgen dat er een fout optreed.

Op vergelijkbare manier kun je met true/false vragen bijvoorbeeld achterhalen:

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

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?

En dan de belangrijkste vraag. Hoe voorkom je SQL Injection?
Ten eerste moet je nooit input van de gebruiker vertrouwen. Niet als we het hebben over SQL queries, maar ook in elke andere situatie waar input gebruikt wordt vanaf de client. Dus niet alleen de input controls op de pagina, maar ook URL’s, api-calls, JSON payloads, etc. Wat zou er bijvoorbeeld gebeuren als ik deze URL aan zou roepen? http://example.com/app/accountView?id=’ or ‘1’=‘1

Je kunt validatie toevoegen, maar dat heeft zo zijn limitaties. Clientside validatie is goed voor de user experience, maar biedt qua beveiliging niets, want daar is makkelijk omheen te werken. Ook met validatie op de server zijn er vaak mogelijkheden om de validatie te ontwijken, bijvoorbeeld door de gegevens te encoderen zodat de validator het niet herkent.

De beste oplossing is daarom gebruik te maken 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 een stukje C# code, maar vergelijkbare methodiek is beschikbaar voor andere talen zoals PHP. In de query test wordt niet meer direct de input van de gebruiker geplakt, maar staat een parameter “@price”. In de voorlaatste regel wordt de waarde van de parameter gevuld met de inhoud van de searchPrice variabele die de user input bevat. Vervolgens wordt de parameter aan het commando gekoppeld zodat deze veilig kan worden uitgevoerd.

Meer informatie over beveiliging van webapplicaties?

Softwareontwikkeling blijft zich evolueren en dat geldt ook voor hackers. Het is dus belangrijk om te blijven evalueren en verbeteren op het gebied van (data)beveiliging. De OWASP Top 10 kan daar goed bij helpen, maar uiteraard is het belangrijk om professionele software ontwikkelaars en/of beveiligingsspecialisten te betrekken bij de ontwikkeling en het onderhoud van software oplossingen. Mocht u meer willen weten over beveiliging van webapplicaties of SQL Injection, neem dan contact op met Christian. Hij praat u graag bij.

Interesse in een gesprek?

neem contact op met Christian Peeters

Laat uw gegevens achter

We nemen contact met u op!

Zie onze privacyverklaring.