Veilig gegevens verwerken

Inleiding

Webappbeveiliging (webapplicatiebeveiliging) is een deelgebied van informatiebeveiliging. Informatiebeveiliging gaat over mensen, die door technische handelingen informatie en software-systemen verstoren. Veiligheid onderscheidt zich van betrouwbaarheid of andere kwaliteitseigenschappen van software-oplossingen door de unieke rol van mensen. Het gaat om mensen die bewust software-systemen proberen te verstoren. Bij andere problemen over kwaliteitseigenschappen (bijvoorbeeld te hoge hardwaretemperatuur, neerslag, te weinig geheugen, slechte gebruikersvriendelijkheid, onverwachte crashes, etc.) is dat niet zo.

Webappbeveiliging heeft veel onderwerpen gemeen met gegevensbescherming, in spreektaal privacy genoemd. Als in je webapp tot mensen herleidbare informatie of informatie over mensen voorkomt of rondstroomt (persoonsgegevens), dan moet je over gegevensbescherming nadenken. Daarbij betrek je soms informatiebeveiliging, maar soms staat privacy daar los van.

📖 Security should not be so scary!

Een kwetsbaarheid is een ‘beveiligingsgat’ dat in theorie kan worden uitgebuit door een aanvaller met een aanval met (een) aanvalstechniek(en). De situatie waarin er een mogelijke aanvaller i.c.m. een kwetsbaarheid is, heet een dreiging. Die gevallen waarin de aanval echt gebeurd is en geslaagd, heten incidenten. Naar aanvallen die wel gebeurd zijn maar niet geslaagd, verwijs je nog gewoon met aanvallen. Het risico van een dreiging is de kans dát de dreiging een incident wordt, maal het gevolg áls de dreiging een incident wordt (m.a.w. de ernst).

Waarom je over webappbeveiliging leert

Webapps zijn een van de belangrijkste leveringsvormen van software aan gebruikers. Desktopsoftware en mobiele apps zijn ook belangrijk. Maar als er een desktop- of mobiele softwareoplossing voor bijvoorbeeld een streamingmediaproduct is, is er meestal ook een webapp. Andersom is dat minder vaak. Beveiligingsincidenten met webapps vormen in de praktijk de grootste risico’s.

Webappbeveiliging bestaat in deze course uit vier stappen:

  1. De onderdelen van de webapp in kaart brengen.
  2. De risico’s in kaart brengen.
  3. De maatregelen tegen die risico’s vastleggen.
  4. De maatregelen doorvoeren.

De risico’s in kaart brengen

Je wilt concreet weten wat voor dreigingen er zijn, om het risico ervan te bepalen. Hierbij kan je putten uit de ervaring van eerdere projecten waarin dezelfde onderdelen gebruikt werden. Daarvoor heb je in de vorige stap de onderdelen in kaart gebracht. Dan moet je nog wel bepalen op welke manieren die onderdelen aangevallen zouden kunnen worden. Vervolgens moet je nagaan welke aanvallers (wanneer) die dreiging mogelijk zouden omzetten in aanvallen. Zo bepaal je de kans en het gevolg van de dreiging, en daarmee het risico.

Aanvalstechnieken

Aanvalstechnieken zijn te categoriseren, bijvoorbeeld heel technisch en gedetailleerd of juist algemeen. Het begrip aanvalstechniek hangt samen met een kwetsbaarheid. Een vuistregel is dat er één aanvalstechniek voor meerdere kwetsbaarheden bestaat, omdat de kwetsbaarheden vaak op elkaar lijken. Je zoekt in je webapp dus naar kwetsbaarheden vanwege aanvalstechnieken, in plaats van andersom.

In je eigen webapp spelen bijvoorbeeld minstens drie algemene aanvalstechnieken. Gezaghebbende bronnen over webappbeveiliging komen o.a. van de organisatie OWASP. In hun OWASP Top 10 onderbouwen zij de tien aanvalstechnieken die over het algemeen de grootste risico’s vormen.

Kans

Natuurlijk is de kans op allerlei dreiging-risico’s, waarbij jouw webapp ongericht een doelwit is, zelden echt nul. Het ligt eraan wat voor aannames je doet. Tegen wie wil je beschermen? Tegen de NSA? Dan zijn alle theoretische dreigingen realistisch. Tegen je zusje? Dan waarschijnlijk niet (of je zusje werkt daar, en heeft een hekel aan je ...).

☠️ Er is een groot verschil dat de algemene kans op webapp-aanvallen veel hoger maakt dan veel leken beseffen: webapps zijn virtueel. Jouw webapp staat open voor een veel grotere groep aanvallers dan een fysiek beveiligd/bewaakt voorwerp. Hoe erg zou het zijn als jouw fietsslot door iedereen in de wereld tegelijk zou kunnen worden gekraakt? Ook zijn aanvallen mogelijk onzichtbaarder en sneller. Bijvoorbeeld, als het kraken niet een kwartier duurt met een betonschaar, maar 500 ms op een Chromebook van een jongetje van twaalf?

Naast de algemene kans, is er de specifieke kans dat mogelijke aanvallers jou nu willen aanvallen. Het is bekend dat allerlei landen in de wereld voortdurend aanvallen ondernemen. Maar dan meestal tegen waardevolle doelwitten, zoals topmensen. Vanuit gegevensbescherming bezien, liggen de risico’s weer anders. Stel jouw webapp is voor afspraakjes. Vanuit beveiliging bezien is die webapp niet bijzonder, want het gevolg van risico’s van dreigingen is beperkt (denk aan: er vallen geen doden, de maatschappij komt niet tot stilstand, er worden geen geldverliezen geleden, etc.). Maar vanuit gegevensbescherming kan het gevolg groot zijn. Veel mensen vinden het erger als hun liefdesleven op straat ligt dan als bijvoorbeeld hun etenbestelhistorie op straat ligt. Zelfs een land kan jouw afspraakjeswebapp willen aanvallen, bijvoorbeeld om affaires van topmensen te ontdekken, om die daarmee onder druk te kunnen zetten.

Besef ook dat de kans plotseling kan toenemen door overmacht. Toen bijvoorbeeld Donald Trump op Twitter actief werd en ook ineens veel bekender en invloedrijker werd, werd Twitter ook actiever aangevallen. ⚠️ Als je webapp eenmaal (uit)ontwikkeld is, blijkt het in de praktijk erg lastig en tijdrovend om ineens nog voldoende beveiligingsmaatregelen te door te voeren. Dan hangt de webapp al af van allerlei vrijheid of ongeregeldheid die je alsnog moet wegnemen.

Gevolg

Het technische gevolg van een dreiging kan bijvoorbeeld zijn dat data uitgelezen wordt. Meestal is data kunnen veranderen erger dan uitlezen. Maar als een aanvaller bijvoorbeeld een wachtwoord kan uitlezen, kan de aanvaller waarschijnlijk uiteindelijk ook data veranderen. Het gaat dus om het ernstigste wat uiteindelijk mogelijk is, los van andere dreigingen. Het kunnen uitvoeren van broncode betekent vaak zowel kunnen lezen als schrijven, en is dus ernstig. Als de aanvaller handelingen kan doen (of broncode kan uitvoeren) als een gebruiker met de hoogste rechten (bijvoorbeeld een beheerder) dan is dat ook ernstiger dan wanneer de aanvaller alleen iets kan doen binnen een normaal gebruikersaccount. Het maatschappelijke gevolg van een dreiging in DigiD is hoger dan van een (technisch vergelijkbaar) inlogsysteem voor, zeg, voorraadbeheer van cafés. In een onderbouwing van het gevolg(en) leg je dus ook uit wat voor soort gevolg jij bedoelt.

Documentatie: risicoanalyse en dreigingsmodel(len)

De risico’s van je webapp leg je vast in een risicoanalyse, vaak een document. Daarop aansluitend maak je één of meer dreigingsmodellen, vaak in diagramvorm. Op deze manier zijn de uitgangspunten van jouw webappbeveiliging duidelijk beschreven voor alle belanghebbenden.

Kans en gevolg, en daardoor het risico, kan je kwantitatief (met getallen) uitdrukken. Maar het is vaak (ook binnen deze course) al voldoende om een categorische ordening (bijvoorbeeld van ‘laag’, ‘middelmatig‘, ‘hoog’) te gebruiken. Zolang je ordening maar onderbouwd is.

Je risicoanalyserapport vormt de basis en de bron van informatie over je webappbeveiliging. Het bevat de risico's, een inventaris van de entiteiten die een rol spelen in beveiliging (zoals aanvallers, kwetsbaarheden, etc.), en tot slot de maatregelen die je neemt tegen het risico. Het rapport zal deels erg technisch zijn. Als de doelgroep breder is dan alleen deskundigen, zouden zulke ingewikkelde zaken daarin aanvullend samengevat kunnen worden.

In de volgende paragraaf alvast een voorbeeld van een risicobeschrijving.

R1: SQL-injectie vanuit views met logincomponent

Er in de PHP-broncode van het beroepsproduct géén gebruikgemaakt van prepared statements in het onderdeel dat loginformulieren verwerkt. De aanvaller is een cybercrimineel die de gegevens uit de gebruikerstabel wil kopiëren om er geld mee te verdienen. Aangezien de loginverwerkings-URL open moet staan voor iedere bezoeker, niet alleen gebruikers, kan hij de kwetsbaarheid makkelijk en ontraceerbaar uitbuiten.

Tabel 1: SQL-injectie.
Risico R1: SQL-injectie vanuit views met logincomponent.
Aanvalstechniek Broncodeinjectie: SQL.
Kans Hoog: staat op nummer één van de OWASP Top 10 (A1:2017-Injection, z.d.).
Gevolg Hoog: PHP-broncode die het SQL statement uitvoert heeft minstens de rechten om de hele gebruikerstabel uit te lezen.

Een doel van een dreigingsmodel kan zijn om de risico’s inzichtelijk te maken voor alle belanghebbenden, en in verband te brengen met concrete aanvallers, dreigingen, aanvallen en incidenten. Je baseert je in het model op de informatie uit het risicoanalyserapport. Het is prima om je dreigingsmodel pas af te ronden nadat je de maatregelen hebt opgeschreven. (Over maatregelen leer je meer in de volgende paragraaf.) ⚠️Deze documenten mogen dus niet in tegenspraak zijn. Verzeker dat door traceerbare codes te gebruiken voor alle verwijzingen naar dingen.

De maatregelen vastleggen

In je risicoanalyserapport moet je ook een hoofdstuk wijden aan welke maatregelen je neemt om elk van deze risico’s te beheersen, en het bedoelde effect daarvan. Het beheersen van het risico kan variëren van ‘geen maatregel’ (dwz. acceptatie van het risico) tot een hele lijst bestaande uit technische en organisatorische maatregelen. Het gaat erom dat je vastlegt en onderbouwt wat je doet, niet dat alle risico’s zonder meer perfect beheerst moeten lijken.

Technische maatregelen

Je hebt voor de onderdelen al beveiligingsadvies van de leverancier opgezocht. Daarin vind je meestal voldoende technische maatregelen voor een minimale beveiligingskwaliteit.

Hiernaast zijn er bronnen van derden over het beveiligen van specifieke onderdelen. Voor PHP zijn er bijvoorbeeld:

Het Nederlandse Centrum voor Informatiebeveiliging en Privacybescherming (CIP) ontwikkelt normen en adviezen hoe je webappbeveiliging dient te waarborgen, zeker als je zaken wilt doen met overheidsorganisaties. Je kan deze bron snel doorlezen om een idee te krijgen van goede beveiligingsvereisten. Je hoeft deze informatie niet uit je hoofd te leren, maar je kan hierin zoeken als je de informatie nodig hebt.

📖 Grip op Secure Software Development (SSD) Beveiligingseisen voor (web)applicaties

Organisatorische maatregelen: management en werkprocessen

Naast technische maatregelen nemen, is ook het management van webappbeveiliging erg belangrijk.

Het voorkomen van kwetsbaarheden

Voorkomen is beter dan genezen. Er zijn een hoop maatregelen die afspraken vragen binnen het ontwikkelteam en tussen de leverancier en opdrachtgever.

Het is belangrijk om alle redelijke beperkingen van het begin af aan in te stellen, zowel binnen het product als in de manier van samenwerken binnen het ontwikkelteam, zodat de kans dat nieuwe kwetsbaarheden worden toegevoegd tijdens ontwikkeling zo laag mogelijk is. Je kan deze bron snel doorlezen om een idee te krijgen van goede maatregelen die een organisatie kan nemen. Je hoeft deze informatie niet uit je hoofd te leren, maar je kan hierin zoeken als je de informatie nodig hebt.

📖 Grip op Agile Secure Software Development (SSD) - Agile Security Management

Het begrippenkader (Nederlands- en Engelstalig) van webappbeveiliging

Alle begrippen, dwz. concepten die niet naar een specifieke technologie verwijzen:

  • Aanval (attack)
  • Aanvaller, actor (threat agent, threat actor)
  • Aanvalsoppervlak (attack surface)
  • Afgestemde kwetsbaarheidsopenbaarmaking (AKO) (coordinated vulnerability disclosure (CVD))
  • Aanvalstechniek (middel, attack vector)
  • Dreiging (threat)
  • Dreigingsmodel (threat model)
  • Beschikbaarheid (availability)
  • Beveiligingslaag (isolation layer, security boundary)
  • Incident (exposure, breach, soms ook, minder correct, exploit)
  • Kwetsbaarheid (vulnerability, security weakness),
  • Maatregel (control)
  • Oorsprong (origin)
  • Persoonsgegeven (personal data, PII)
  • Veiligheid (security, niet: safety)

Voorbeelden van technische maatregelen met PHP

Webappbeveiliging is een heel eigen vakgebied. Als beginnende webontwikkelaar moeten we een paar basisregels ter verdediging kennen. Deze zijn:

  • Formulierinvoer screenen.
  • Wachtwoordbeleid en password hashing.
  • Voorkomen dat SQL-broncode vervuild raakt met aanvalsbroncode.

Invoer sanitizen

Een website wordt kwetsbaar zo gauw bezoekers iets kunnen invoeren en deze invoer op de webserver verwerkt wordt. Om te voorkomen dat iemand de webapp beschadigd door gevaarlijke broncode binnen te sluizen, kan je de invoer beperken en opschonen.

Aanpassingen hievoor in de HTML-broncode van formulieren geven geen garantie tegen misbruik.

Het beperken van de invoer via HTML-attributen zoals type, maxlength, required, min, max helpen de gebruiker om foutieve invoer te voorkomen. Maar alle broncode op de client, voor ons meestal de browser, is voor gebruikers toegankelijk en daarom ook te manipuleren door kwaadwillende gebruikers.

We moeten dus ook aan de serverkant maatregelen nemen om te kijken, of wat iemand heeft ingevuld, door de beugel kan. Wat wel of niet door de beugel kan is helemaal afhankelijk van welke informatie wij van de gebruiker nodig hebben.

  • Moet een veld ingevuld zijn?
  • Verwachten we pure tekst, zoals bijvoorbeeld bij een naam?
  • Verwachten we cijfers of getallen?
  • Mag er slechts uit een reeks vaste antwoorden worden gekozen?
  • Wat is een maximale lengte van de gevraagde informatie?
  • Mogen er speciale tekens tussen zitten?
  • Mag er broncode tussen zitten?

In het kader van deze cursus gaat het vooral om bewustwording. Toch hier een paar PHP functies die we kunnen gebruiken om de invoer te checken of kwaadaardige broncode onschadelijk te maken. De details van deze functies staan op de documentatie van PHP.net (klik op de volgende links).

  • empty() om te kijken of bijvoorbeeld een $_GET of $_POST-variabele leeg is.
  • is_numeric() om te kijken of iets een getal is.
  • strlen() om de lengte van de invoer te checken.
  • strtolower() om van alles kleine letters te maken (voorkomt mismatches).
  • htmlspecialchars() maakt er HTML-entities van gevoelige tekens en voorkomt dat tekst als broncode geïnterpreteerd kan worden.
  • strip_tags() om HTML-tags en hun attributen uit de invoer te strippen.

Password hashen

Vandaag de dag is het absoluut not done om wachtwoorden zomaar in een database op te slaan. Iedere beheerder zou op deze manier de wachtwoorden van gebruikers in kunnen zien en bij een lek liggen de inloggegevens op straat.

Een hash betekent dat een string volgens bepaalde regels verhaspeld wordt tot een hashwaarde, een soort unieke code. Door deze code overal te gebruiken in plaats van het wachtwoord zelf, wordt de kans dat onbevoegden het wachtwoord letterlijk kunnen aflezen kleiner. Deze regels zijn in algoritmes vastgelegd. Het PASSWORD_DEFAULT-algoritme bijvoorbeeld produceert een string van 60 tekens.

password_hash($password, PASSWORD_DEFAULT)

Om te kijken of een ingevoerd wachtwoord overeenkomt met het gehashde wachtwoord gebruiken we de functie:

password_verify($password, $versleuteldpassword)

Een hash van het woordje: test

$2y$10$PURzDKzhHPssBFBz7sfSUOUls8LeU4b01owEeRzV9oHfD2TvdZ3dq

Om een hash te maken en uit te lezen uit de database:


  $wachtwoord = $_POST['wachtwoord'];
  $hash = password_hash($wachtwoord, PASSWORD_DEFAULT);
  // hash opslaan in database, niet wachtwoord.

  // user verifieren.

  // wachtwoord ophalen uit database
  if (password_verify($ingevoerd_wachtwoord, $query['wachtwoord'])) {
    // login met sessie
  }

Prepared statements gebruiken

Prepared statements zijn primair in het leven geroepen om database queries zo efficient en optimaal mogelijk uit te voeren. De statement kan nu meerdere keren uitgevoerd worden zonder dat de Database de SQL-statement nog een keer moet interpreteren.

Prepared statements zijn ook goed bestend tegen SQL injection.

SQL injection is een groot beveiligingsrisico voor websites. Gegevens die een gebruiker invoert worden meestal in een database verwerkt. En deze database kan gehackt worden door in een invoerveld kwaadaardige broncode toe te voegen.

Stel je een klein formulier voor waar je een product_id kan invoeren om dit product uit de productentabel te verwijderen.


Product verwijderen

*Voorbeeldformulier*

Aan de serverkant wordt dit formulier met de volgende broncode verwerkt:

$id = $_POST['id'];
$verbinding->query("DELETE FROM producten where id = $id");

Als nu iemand in het vakje bij ID invult: 105 OR 1 = 1 komt in de SQL-broncode te staan.

DELETE FROM producten where id = 105 OR 1 = 1 --

Dit betekent dat uit de tabel alle producten verwijderd worden omdat 'where 1 = 1' altijd waar is.

Dit is slechts een simpel voorbeeld en niet erg voor de hand liggend, maar laat zien hoe SQL Injection werkt. Prepared statements zijn er om dit te voorkomen.

Prepared statements in PHP

Bij PDO hebben we de mogelijkheid met prepared statements te werken. Een voorbeeld met daarna uitleg:

$niveaucode = $_POST['niveaucode'];
$omschrijving = $_POST['omschrijving'];

// Een string met speciale karakters.
$sql = "INSERT INTO Niveau (niveaucode, omschrijving) VALUES (:niveaucode, :omschrijving)";

// Wordt hier in `prepare()` gegooid
$query = $verbinding->prepare($sql);

// Nu kan je de waardes er aan toewijzen met `->execute()`.
// Door de placeholders is de volgorde NIET van belang
$query->execute([':niveaucode' => $niveaucode, ':omschrijving' => $omschrijving]);
  • Hier wordt de query in twee opdrachten verdeeld: in prepare() en execute().
  • Bij prepare() uitgevoerd op de databasehandler $verbinding wordt een SQL-querystring aangemaakt met op de plaatsen, waar gebruikersinvoer verwerkt wordt, placeholders (:niveaucode, :omschrijving). Dit geeft aan dat er op die plaatsen telkens één string mag worden ingevuld.
  • Bij execute() van het object dat eerst met prepare() is aangemaakt wordt nu de voorbereide SQL-broncode uitgevoerd en wel met waardes die aan de placeholders gekoppeld worden. Dat koppelen gebeurt in de vorm van een associatieve array.
  • Het is ook mogelijk met de methode bindParam() te werken om waardes aan plaatshouders te koppelen.

Als alternatief kunnen ook vraagtekens als plaatshouders gebruikt worden. Bij execute() telt dan de volgorde van de array-elementen.

$niveaucode = $_POST['niveaucode'];
$omschrijving = $_POST['omschrijving'];

// Een string met speciale karakters.
$sql = "INSERT INTO Niveau (niveaucode, omschrijving) VALUES (?, ?)";

// Wordt hier in `prepare()` gegooid
$query = $verbinding->prepare($sql);

// Nu kan je de waardes er aan toewijzen met `->execute()`.
// Door de placeholders is de volgorde WEL van belang!
$query->execute([$niveaucode, $omschrijving]);

👩‍🎓 Extra: Bekijk het advies van een beveiligingsexpert

Vind je webappbeveiliging door een technische bril érg interessant? Dan mag je als extraatje ook naar deze uitleg kijken van ‘de dikste yogainstructeur op de planeet’ 😁.

🎬 The OWASP Top Ten Proactive Controls - Jim Manico

Vind je ook organisatieprocessen interessant? Kies dan voor het profiel Infrastructure & Security Management (ISM). 😎