Bij Computest Security worden we blij van code. Met enige regelmaat nemen we een nieuwe code base onder de loep, van websites tot mobiele apps. Vandaag bekijken we welke lessen we hieruit hebben kunnen trekken en duiken we in de wondere wereld van de frontend frameworks. Waarom zou je voor je website überhaupt een framework willen gebruiken? En wat zijn de belangrijkste dingen die we mis zien gaan als applicaties gebruik maken van zo'n frontend framework?
Wat is een frontend framework?
Een framework is een raamwerk, een startpunt om je website op te ontwikkelen. Een framework standaardiseert de manier waarop je je software, de code, structureert en neemt je werk uit handen door veel dingen die elke website wel nodig heeft al standaard aan te bieden. Denk hier bijvoorbeeld aan het opslaan van data of het opbouwen van pagina's op de website. Dat laatste is een mooi voorbeeld van wat een framework voor de "voorkant" van de website te bieden heeft. Omdat het framework bedoeld is voor de voorzijde van de website noemen we het ook wel een frontend framework. Op basis van onze ervaringen met kwetsbaarheden in de praktijk, geven we in dit blog de drie belangrijkste tips om op een veilige manier met frontend frameworks om te gaan.
1. Wantrouw je frontend
De frontend heet de frontend, omdat dit het deel is van de code van de website dat op de computer, laptop of mobiel van de bezoeker draait. Dat betekent ook dat je de frontend dus eigenlijk niet kan vertrouwen. Iemand met kwade bedoelingen kan eenvoudig de broncode van de frontend bekijken of aanpassen. Daarom is het belangrijk dat de achterkant van de website, de backend, niet op de frontend vertrouwt om beveiligingscontroles uit te voeren, maar dit altijd zelf doet. Denk dan bijvoorbeeld aan controleren of iemand is ingelogd, of controleren of een ingelogde gebruiker bij gevoelige data mag. Verder is de broncode van de frontend openbaar. En dus is het ook zeker geen plek voor gevoelige informatie of andere geheimen, zoals wachtwoorden of API-sleutels.
2. Ken de beperkingen van je framework
Een frontend framework is ontzettend nuttig. En met name om één type kwetsbaarheid in het bijzonder te voorkomen waar een derde van de websites door geplaagd wordt. Deze kwetsbaarheid noemen we cross-site scripting. Als een website kwetsbaar is voor cross-site scripting, dan kan een kwaadwillende met behulp van een stukje kwaadaardige code andere bezoekers van de website aanvallen. Het is in de praktijk vaak zo dat de kwaadwillende niet zomaar iedereen op de website kan aanvallen, maar dat het beoogde slachtoffer bijvoorbeeld eerst nog op een specifiek linkje moet klikken waar de aanvaller de kwaadaardige code in heeft opgenomen.
Hoe dan ook zijn frontend frameworks een nuttig instrument om cross-site scripting kwetsbaarheden te voorkomen. Veel frameworks zorgen er namelijk standaard voor dat de juiste encoding wordt toegepast. Zo wordt ervoor gezorgd dat de invoer, bijvoorbeeld uit dat linkje van net, niet per ongeluk wordt geïnterpreteerd als code, maar alleen aan de bezoeker wordt getoond.
Desondanks zijn frontend frameworks geen magische oplossing en zijn er stiekem best veel uitzonderingen waar je als ontwikkelaar rekening mee moet houden. We duiken zo een paar alinea's de technische details in. Als je geen ontwikkelaar bent, kun je ook verder lezen bij kopje 3 over updates.
2.1. Een stukje theorie over frameworks
Cross-site scripting ontstaat doordat gebruikersinvoer op een plek in de frontend terechtkomt waar deze niet als tekst wordt geïnterpreteerd, maar bijvoorbeeld als HTML of JavaScript. Stel dat we bijvoorbeeld een zoekfunctionaliteit bouwen en aan een bezoeker tonen hoeveel resultaten de zoekterm opgeleverd heeft, dan zouden we zomaar kunnen vergeten om de zoekterm van de gebruiker netjes te HTML encoden. Zoals je kunt zien in onderstaande afbeeldingen, kunnen we dan een linkje maken waar kwaadaardig JavaScript in zit. Zodra iemand op dat linkje klikt, wordt deze code uitgevoerd. Met JavaScript kun je bijna net zo veel als het slachtoffer en dus kun je als kwaadwillende bij gevoelige data van het slachtoffer.
Grofweg zijn er drie basisvarianten van cross-site scripting. Dit onderscheid maken we op basis van waar de gebruikersinvoer belandt. In bovenstaande voorbeeld was dat tussen de tags van een element, in de 'innerHTML', maar cross-site scripting kan ook ontstaan door gebruikersinvoer op te nemen in een HTML-attribuut.
Tot slot is er nog een laatste, bijzondere vorm die gebruik maakt van speciale URL-prefixes, zoals 'javascript:'.
2.2 Cross-site scripting en frontend frameworks
Sommige frameworks doen erg hun best om bescherming te bieden tegen alle drie de basisvarianten van cross-site scripting die we hierboven hebben gezien. Andere frameworks bieden alleen bescherming tegen de eerste variant. Laten we de drie verschillende frameworks bekijken.
2.2.1 Angular
Zolang je in Angular netjes interpolation gebruikt ({{ }}) dan zit je goed. Angular is namelijk best slim en herkent de verschillende contexten van zojuist. Wel moet je scherp zijn op het volgende:
- Voorkom dat je direct, buiten Angular om, met DOM-elementen interacteert. Zo kun je bijvoorbeeld in Angular direct de DOM manipuleren met behulp van ElementRef. Je maakt dan direct gebruik van ingebouwde API's in de browser waar je niet altijd veilig gebruikersinvoer in kunt meegeven.
- Voorkom dat je de ingebouwde bescherming van Angular omzeilt. Je kunt in Angular expliciet aangeven dat een bepaalde waarde veilig is, bijvoorbeeld met een functie als bypassSecurityTrustHTML. Zoals de naam al aangeeft, bypass je dan de escaping van Angular en loop je het risico op een cross-site scripting kwetsbaarheid.
- Voorkom dat je templates dynamisch genereert, bijvoorbeeld door nog handmatig strings aan templates vast te plakken (string concatenation) of door de templates op te bouwen met nog een andere templating taal. Escaping vindt namelijk pas plaats bij het renderen van de template. De template zelf moet daarom te vertrouwen zijn. Als je gebruikersinvoer in het template opneemt, dan ben alsnog kwetsbaar voor cross-site scripting.
2.2.2 Vue.js
Ook Vue.js helpt je flink op weg. Als je netjes text interpolation gebruikt ({{ }}) dan zit je in principe goed. Gebruikersinvoer tussen de brackets wordt ge-HTML-encode. Dat betekent dat je gebruikersinvoer veilig tussen twee HTML-tags kunt opnemen. Let wel op de volgende punten:
- In dynamic attribute bindings kun je in de meeste gevallen veilig gebruikersinvoer kwijt. Alleen even opletten met attributen als 'src' en 'href', want Vue.js encode in de attribute bindings alleen de double quotes ("), en dus blijft een prefix als 'javascript:' gewoon staan.
- Daarnaast kun je in Vue.js met het v-on directive ook handlers hangen aan events. Maar, je zou ook om Vue.js heen kunnen werken en direct een handler kunnen hangen aan het 'native' event-attribuut. Denk dan bijvoorbeeld aan onclick of onmouseover. Maar let op, dat is best een beetje tricky, want gebruikersinvoer die in zo'n attribuut terechtkomt wordt geïnterpreteerd en uitgevoerd als JavaScript. Met text interpolation vindt er weliswaar HTML encoding plaats, maar dat doet niet zo veel. Gebruikersinvoer belandt namelijk niet in een HTML-context, maar in een JavaScript-context en daarvoor is een andere aanpak nodig om cross-site scripting te voorkomen.
- Net zoals bij Angular kun je in Vue.js ook direct de DOM manipuleren, bijvoorbeeld met het directive v-html, een render function of met JSX. Gebruikersinvoer die je op die manier verwerkt, wordt als HTML geïnterpreteerd. Deze functies zijn dus risicovol om in combinatie met (potentiële) gebruikersinvoer te gebruiken.
- Tot slot is het net zoals bij Angular gevaarlijk om templates dynamisch te genereren. Ook in Vue.js geldt dat escaping plaatst vindt op het moment dat de template gerenderd wordt. Als het template zelf niet te vertrouwen is, omdat er gebruikersinvoer aan wordt toegevoegd, loop je het risico op cross-site scripting kwetsbaarheden.
2.2.3 React
Met React geldt een vergelijkbaar verhaal. Als je JSX gebruikt om HTML-elementen te definiëren wordt content tussen curly brackets ({ }) geëvalueerd als JavaScript expression en daarna netjes ge-HTML-encode. Gebruikersinvoer kun je daarom veilig tussen HTML-element tags opnemen. Wederom zijn er aandachtspunten:
- Ook React escapet binnen attributen enkel de double quotes ("). Prefixes als 'javascript:' blijven daarom opnieuw gewoon staan en dus is het opletten met attributen als 'src' en 'href'.
- Net zoals bij Angular en Vue.js kun je in React direct de DOM manipuleren, bijvoorbeeld met dangerouslySetInnerHTML of door direct met DOM-elementen te interacteren via findDOMNode of createRef. De escaping die React standaard toepast is bij deze functies niet van toepassingen en dus is het risico op cross-site scripting kwetsbaarheden aanwezig.
- Tot slot is een vrij veelvoorkomend patroon om in React componenten te instantiëren door de spread operator (...) te gebruiken (bijvoorbeeld: <div { ...properties }>). Dat is best een beetje een risicovolle strategie, want een gebruiker die het properties-object kan aanpassen, kan met het zetten van 'dangerouslySetInnerHTML' eenvoudig JavaScript injecteren. Ook dan heb je te maken met een cross-site scripting kwetsbaarheid.
3. Updates
Software updates en dependency-beheer zijn altijd een security-uitdaging. Dit geldt zeker ook voor je frontend framework. In de eerste plaats is het belangrijk om periodiek te controleren of er nog nieuwe kwetsbaarheden bekend zijn geworden. Dat is best vaak het geval en het zou zomaar kunnen dat de kwetsbaarheid ook voor jullie van toepassing is. Tooling kan hier een uitkomst bieden. Zo kunnen veel package managers automatisch controleren op bekende kwetsbaarheden (bijvoorbeeld npm audit) en zijn er ook dependency scanners die geautomatiseerd pull requests kunnen aanmaken voor updates (zoals Dependabot).
Tot slot is het ook absoluut de moeite waard om een beetje kritisch te zijn op de software waar je afhankelijk van bent. Elke extra bibliotheek betekent meer aanvalsoppervlak, meer functionaliteiten waar een onbekende kwetsbaarheid in kan zitten, meer software die je up-to-date moet houden en meer leveranciers die potentieel zelf een keer gehackt kunnen worden. Kortom, redenen genoeg om het aantal software-dependencies tot het minimum te beperken én alleen software te gebruiken van vertrouwde bronnen.
Conclusie
Veilig een frontend framework gebruiken? Dat doe je zo:
- Wantrouw je frontend. Voor geheimen en belangrijke security-checks, gebruik je de backend.
- Wees je bewust van de beperkingen van je framework. Een framework is alleen veiliger als je het framework ook op een veilige manier gebruikt.
- Houd software-updates in de gaten en zorg dat je beveiligingsupdates op tijd installeert.
Benieuwd of jouw frontend veilig in elkaar zit? Laat je broncode door ons reviewen. We doen diepgaand onderzoek waarin we jouw codes (en configuraties) kraken om alle kwetsbaarheden te ontdekken. Ook geven we feedback op architectuur- en ontwerpkeuzes van de code base, zodat je de kwetsbaarheden kan oplossen. Neem contact op via info@computest.nl.