Lesmateriaal Webapplicatie Beveiliging - SQL

Lesmateriaal Webapplicatie Beveiliging - SQL

Voorwoord

In deze blogpost zal ik verschillende methodes van het uitbuiten van beveiligingslekken beschrijven. Dit is in geen enkele mate toestemming om deze injecties, uitbuitingen en hacks uit te voeren en dient enkel als voorbeeld om het idee van beveiliging te onderbouwen. Om deze reden ga ik niet heel diep in op de hacking onderdelen en meer in op het idee van beveiliging.

Deze blogpost is de eerste van een vier-delige reeks aan blogposts over de beveiliging van webapplicaties, vanuit het oog van een docent. Veel leesplezier!

SQL Beveiliging

Inleiding

Wat is SQL? SQL is de taal waarmee communicatie wordt uitgevoerd met een database. Het staat voor Structured Query Language en is de gestandaardiseerde taal voor communicatie met databases volgens ANSI (American National Standards Institute). SQL is een taal waarin zogenaamde Queries (een zin aan commando’s) worden gestuurd naar en uitgevoerd door de database. Hiermee kan data worden opgevraagd, maar ook data worden toegevoegd, aangepast of verwijderd uit de database. Een voorbeeld hiervan is de volgende Query:

SELECT * FROM users WHERE id = 10

Het commando die hier wordt uitgevoerd is SELECT, dit commando selecteert bepaalde informatie uit de database en geeft dit terug. Wat er wordt geselecteerd, dat wordt bepaald door de velden, tabel en condities.

Tabellen en velden

Een database is opgesteld uit tabellen. Deze tabellen zien er hetzelfde uit als degenen die worden gemaakt in Word, met rijen en kolommen. De kolommen worden ook wel velden genoemd. Een rij is vervolgens een zogenaamde Entry aan data, een instantie van data in de database. De velden vormen de types data die een Entry heeft. Een goed voorbeeld hiervan zijn de velden die kunnen worden gebruikt bij het beheren van gebruikers. In dit voorbeeld nemen we een tabel met de naam “users” (net zoals in het Query voorbeeld hierboven).

id name password email
8 hbietsen wachtwoord123 hbietsen@company.nl
9 mkool huisdier567 mkool@company.nl
10 rerikson beveiligd123 rerikson@company.nl


De velden die worden gebruikt zijn “id”, “name”, “password” en “email”. De bovenste rij geeft de namen van de velden aan (deze rij tellen we niet mee). Daarna zien we drie gebruikers met elk hun eigen invullingen bij de velden. Elk veld heeft ook een type. Hieronder plaats ik een tabel (niet een databasetabel) met daarin de types van elk veld:

Naam Type Auto Increment
id INT true
name VARCHAR(255) false
password VARCHAR(255) false
email VARCHAR(255) false


Voor het gemak heb ik maar twee data types gebruikt, data types is namelijk weer een apart onderdeel. Het veld “id” heeft het type INT, wat betekent dat het alleen ronde nummers kan bevatten. De optie “Auto Increment” staat aan, dus het betekent dat dit veld automatisch toeneemt wanneer er een nieuwe Entry in de tabel wordt geplaatst. Dus, als er bij de “user” een gebruiker zou worden toegevoegd, dan zou de nieuwe id 11 worden (10 + 1).

De velden “name”, “password” en “email” hebben een VARCHAR(255) type, een String (zin, verzameling van karakters (char)) van variabele grootte, ofwel variabele karakters. Het getal bepaald het maximaal aantal karakters dat in de zin mag komen.

Condities

Vervolgens kunnen we in een Query condities kwijt, om specifieker te zoeken. In het voorbeeld heb ik “WHERE id=10” gebruikt en een asterix bij de velden. De asterix betekent “alles”, dus met dit commando vraag ik alle velden op van de tabel “users”. Vervolgens maak ik de zoekopdracht specifieker door een WHERE conditie toe te voegen. In deze conditie zeg ik dat ik alle velden uit de tabel “users” wil ophalen, waar het veld “id” een waarde heeft die gelijk is aan “10”. In zekere zin wil ik in dit geval dus de gebruiker ophalen waarvan de “id” van de gebruiker gelijk is aan “10”.

Injecties

Onder het kopje Security wil ik het bij SQL hebben over een actie genaamd “SQL Injection”, het injecteren van SQL op een plek in de website waar een database connectie wordt gemaakt. Een veelgebruikte plek waar SQL injecties worden gebruikt is een zoekformulier. Je vult een zoekterm (of meerdere zoektermen) in en vervolgens wordt deze term onder water gebruikt in een SQL Query. Die Query kan er bijvoorbeeld zo uit zien:

SELECT * FROM producten WHERE naam = ‘ . $zoekterm . ’

Het woord “$zoekterm” (die ik ook rood heb gekleurd) wordt dan gevuld met de zoekterm die ik heb ingevuld. Dit kan er als volgt uit zien:

SELECT * FROM producten WHERE naam = ‘t-shirt’

Mijn zoekterm “t-shirt” wordt in dit voorbeeld gebruikt om uit de tabel “producten” de producten te halen, waarvan de naam overeenkomt met het woord “t-shirt”.

Bij SQL injectie wordt deze Query gemanipuleerd om informatie te weergeven wat in eerste instantie niet zou moeten worden weergeven. In dit voorbeeld gebruik ik een andere Query, namelijk een voor een login. In deze query wordt het ingevuld wachtwoord gecontroleerd met het wachtwoord wat in de database staat. Wanneer deze overeenkomt, krijgt het systeem 1 rij terug uit de tabel. Maar wanneer deze niet overeenkomt, dan krijgt het systeem geen enkele rij terug en krijgt de gebruiker een foutmelding. De Query ziet er in dit voorbeeld als volgt uit:

SELECT * FROM users WHERE (naam = ‘hbietsen’ AND password = ‘ . $wachtwoord . ‘)

Om de Query uit te leggen, in eerste instantie worden alle velden uit de tabel “users” opgevraagd. Er wordt vervolgens een conditie toegevoegd om specifieke data terug te krijgen. De gebruikersnaam is al bekend, dus deze wordt ingevuld bij de “naam”. Vervolgens wordt er een AND operator gebruikt om ervoor te zorgen dat beide condities moeten kloppen (AND in het nederlands is EN). In dit geval moet de gebruikersnaam gelijk zijn aan wat er is ingevuld (in dit voorbeeld “hbietsen”) en moet het wachtwoord gelijk zijn aan het door de gebruiker ingevulde wachtwoord. In een normale situatie kan dit er zo uitzien:

SELECT * FROM users WHERE (naam = ‘hbietsen’ AND password = ‘wachtwoord123‘)

Het systeem krijgt 1 rij terug (want er is een gebruiker met zowel de naam “hbietsen” als het wachtwoord “wachtwoord123”) en logt de gebruiker in.

Maar wat nou als wij deze Query zo manipuleren, dat wij kunnen inloggen, zonder het wachtwoord nodig te hebben? Dit kunnen wij doen door de waarde van “$wachtwoord” aan te passen. Wij kunnen hier namelijk “inbreken” in de Query en de condities zo veranderen, dat de conditie in zekere zin altijd klopt. Dit doen, door in het wachtwoordveld van het inlogformulier het volgende in te vullen:

blabla123’) OR 1=1; --

Wanneer we dit invullen, zal de Query van hierboven er als volgt uit zien:

SELECT * FROM users WHERE (naam = ‘hbietsen’ AND password = ‘blabla123’) OR 1=1; --‘)

Nu ziet de Query er ineens ietwat anders uit. Ik zal uitleggen wat hier precies gebeurt, aan de hand van hetgeen wat ik heb ingevuld bij het wachtwoordveld.

Eerst starten we met de zin “blablabla123”. Dit is niks meer dan een gok die voldoet aan eventuele wachtwoord eisen (minimaal zoveel karakters, moet een cijfer bevatten etc.). Deze gok kan eventueel nog worden uitgebreid met een hoofdletter en / of symbool als het inlogformulier hierom vraagt.

Hierna staat er “‘)”. Dit sluit de gegroepeerde conditie van de voorbeeld Query. Hierdoor kunnen we een extra conditie toevoegen. Dit zal ik zo nog verder uitlichten. Hierna staat er een “OR” (in het Nederlands OF). Waar de AND operator ervoor zorgt dat beide condities moeten kloppen, zorgt de OR operator dat maar 1 van de condities hoeft te kloppen. Hierna staat er “1=1”. Dit is de tweede conditie die ik heb toegevoegd. Er staan nu dus twee condities:

(naam = ‘hbietsen’ AND password = ‘blabla123’)

en

1=1

De eerste conditie is omgeven door haakjes, dit betekent dat het een gegroepeerde conditie is. In deze condities zitten namelijk twee condities verwerkt: dat de naam gelijk moet zijn aan “hbietsen” EN dat het wachtwoord gelijk moet zijn aan mijn ingevuld voorbeeld (in dit geval “blabla123”). In weze zijn er dus deze twee condities:

  • De naam moet gelijk zijn aan “hbietsen” EN het wachtwoord moet gelijk zijn aan “blabla123”.
  • 1 moet gelijk zijn aan 1.

Omdat het wachtwoord van de gebruiker met de naam “hbietsen” niet gelijk is aan “blabla123” (deze is immers gelijk aan “wachtwoord123”) zal de eerste conditie helaas falen. Maar, wij hebben nog een conditie toegevoegd. Aangezien 1 altijd gelijk is aan 1, zal de tweede conditie dus wel slagen. En, omdat we een OR operator hebben toegevoegd, hoeft maar 1 van de condities te kloppen. Daarom zal de gehele WHERE conditie kloppen en worden wij toegelaten in het systeem, terwijl we een foutief wachtwoord hebben ingevuld.

Als laatste heb ik nog het volgende toegevoegd: “--”. Dit zorgt ervoor dat alle overige code wordt genegeerd en als een zogenaamde Comment wordt uitgevoerd. Dit zorgt ervoor dat alle symbolen en of overige code die na de invoer worden uitgevoerd, geen foutmeldingen zullen veroorzaken.

Beveiligen

Maar hoe komt het dat deze truuk om in te loggen met elk account niet zo vaak werkt? Een gemiddelde website is gelukkig beveiligd tegen dit soort injecties met verschillende manieren. Onder dit kopje ga ik de verschillende onderdelen langs waarmee beveiliging tegen injecties kan worden toegepast en welke tools er bestaan om hiermee te helpen. Ik ga niet zozeer in een bepaalde programmeertaal voorbeelden geven hoe de beveiliging kan worden toegepast, maar noem meer de manieren hoe er kan worden beveiligd tegen injecties.

Verboden symbolen

De eerste manier (en meteen ook 1 van de meest ingrijpende manieren) is het verbieden van bepaalde symbolen. Dit zijn de symbolen die kunnen worden gebruikt bij SQL injecties. Zoals te zien is in mijn bovenstaande voorbeeld heb ik de symbool combinatie “‘)” gebruikt om de gegroepeerde condities af te sluiten en een eigen commando te injecteren. Door deze symbolen, of de combinatie van deze symbolen te verbieden, kunnen bepaalde injecties worden tegengegaan. Dit heeft voor- en nadelen. Een groot voordeel is dat dit erg makkelijk en snel te implementeren is en daarmee een groot deel van de injecties tegen houdt. Het nadeel is dat je bepaalde gebruikers hiermee benadeeld, want er is een kans dat een gebruiker deze symbolen, of een combinatie van deze symbolen wil gebruiken. De gebruiker kan vervolgens deze symbolen niet invullen en heeft niet het comfort om een eigen wachtwoord te kiezen.

Simulaties

De output van de Query kan ook eerst worden gesimuleerd om te controleren wat de output precies is en of dit gewenst is. Wanneer de gewenste output 1 rij is en bijvoorbeeld alle gebruikersdata wordt teruggegeven, is er iets fout. Het nadeel is dat deze manier nooit echt waterdicht zal zijn, aangezien de Query hoe dan ook wordt uitgevoerd.

Escaping

Bij escaping wordt de ingevoerde data genormaliseerd en “klaargemaakt” voor gebruik. In mijn voorbeeld van de geïnjecteerde Query, worden de “‘)” symbolen gebruikt om de Query te manipuleren en mijn eigen code te injecteren. Bij escaping worden deze symbolen “onschadelijk” gemaakt en toegevoegd in de String die wordt opgestuurd. Voor escaping zijn er vaak standaard functies in talen of frameworks om te gebruiken. Escaping is niet waterdicht, maar werkt voor een erg groot deel en is altijd een zogenaamde Best-Practice.

Permissions

Een goede optie is ook om voor verschillende doeleinden verschillende database gebruikers te maken en deze de rechten te geven die nodig zijn. Dit betekent dat voor een database gebruiker die als functie heeft om inlogformulieren te regelen, geen update, insert of delete rechten hoeft te hebben. Bij het inloggen van een gebruiker wordt er namelijk enkel data gecontroleerd, er wordt niks aangepast, toegevoegd of verwijderd. Dit sluit een boel schadelijke injectie opties uit, maar het laat nog steeds injecties toe binnen de rechten die de database gebruiker heeft. Ook moeten er dan meerdere database gebruikers worden gemaakt en daarmee meerdere connecties moeten worden aangelegd. Dit kan effect hebben op de snelheid van de website en wordt om die reden vaak niet gebruikt.

Conclusie

Zoals hierboven te lezen is is geen van de opties perfect. Maar een goed doordachte combinatie van de opties helpt al ontzettend veel. Daarnaast zijn er natuurlijk ook nog meer, nog technisch opties beschikbaar die ik hier niet heb behandeld. Deze opties zijn meer geschikt voor eigen onderzoek en worden daarom niet benoemd in dit dossier. Uiteindelijk is het erg belangrijk om te begrijpen wat een injectie doet en zelf, in combinatie met de beschikbare opties, logisch na te denken over wat een gebruiker kan invullen.

Lesvoorbereidingsformulier

Lijkt je het leuk om hier een les in geven maar heb je geen idee hoe je moet beginnen? Dan kun je altijd dit lesvoorbereidingsformulier gebruiken die is gemaakt voor 1e jaars studenten van de MBO opleiding Software Development. Al het geschreven materiaal is Open-Source en vrij om te gebruiken.

Lesvoorbereidingsformulier