Afgelopen week presenteerde ik op een event van Testnet. Het publiek bestond voornamelijk uit professionals die zich bezighouden met functioneel en performance testing. Waarin zijn deze vakgebieden nou wezenlijk anders dan de testen die we vanuit security-perspectief uitvoeren? De meeste testdisciplines leunen sterk op het gebruik van tools. De professional is er om de tool te kiezen, op de juiste manier in te regelen en om de uitvoer te controleren en te vertalen naar bruikbare conclusies. Juist dat werkt bij security testing heel anders. De inzet van tools is op dat gebied in veel gevallen nog maar het halve werk.
Bij functioneel en performance testing test je met name wat je gebruiker gaat doen. Je richt je op bepaalde klikpaden en test de kritieke punten hierbinnen. Bij security testing kijk je minstens evenveel naar wat de gebruiker juist níet gaat doen. Maar dat is een onuitputtelijke lijst van mogelijkheden… Je kunt dus ook nooit zeggen dat het 100% veilig is. Je weet immers niet wat je niet weet. Daar zit hem nou ook precies de crux met het gebruik van tools voor security testing. Een tool kan alleen iets testen wat hij al weet, dus op basis van een set regels over bekende soorten kwetsbaarheden op bepaalde plaatsen.
Waarvoor kun je wel testing tools inzetten?
Betekent dit nu dat tools nutteloos zijn bij security testing? Zeker niet. Tools voor security testing hebben hun sterke en zwakke kanten. Waar ze goed in zijn is het vinden van eenvoudige kwetsbaarheden, en ze zijn onvermoeibaar in het zoeken daarnaar. Voorbeelden hiervan zijn injecties (SQL, XSS, XML, …), sessie checks, CSRF, cookie settings, hardening, gebruik van SSL.
Waarvoor kun je geen testing tools inzetten?
Waar je echter zeker geen tools kunt inzetten is voor die kwetsbaarheden waar je een stukje context, begrip en creativiteit bij nodig hebt. Denk aan het bypassen van business rules, onbedoelde state transitions, autorisatiecontroles omzeilen, incorrect gebruik van cryptografie.
Een mooi voorbeeld hiervan is de integratie van een iDEAL betaal-flow in een webshop. Bij zo’n integratie spelen veel security-risico’s een rol, zowel op de gebieden waar test-tooling wel en niet kan worden toegepast.
Beveiligingsrisico implementatie iDEAL bij webshop
Om dit te illustreren heb ik dit voorbeeld uitgewerkt. Voor het gemak versimpelen we de standaard iDEAL Basic-implementatie. De security issues die we tegenkomen zijn echter reëel (en komen we helaas ook vaak genoeg tegen in de praktijk).
Vooraf hebben de webshop en de payment provider onderling een secret afgesproken voor het cryptografisch ondertekenen (signen) van berichten, en een aantal URLs waarop ze elkaar kunnen bereiken. Hierdoor kunnen ze controleren of de parameters die worden uitgewisseld, niet onderweg zijn aangepast. De cryptografische signatures staan in de “sig” parameter in het voorbeeld. Als de betaling slaagt ziet de vereenvoudigde iDEAL flow er zo uit:
1. De shop redirect de gebruiker naar een payment provider en moet in de redirect URL aan de payment provider kenbaar maken om welke shop het gaat, om welk order ID en om welk bedrag. Dit wordt respectievelijk in de parameters o, m en a meegegeven. Deze drie parameters worden samen met een secret gehasht tot een signature in de sig parameter. Bijvoorbeeld:
https://paymentprovider/?o=5432&m=1337&a=500&sig=bacb96b16faa158ac9e985a59408ae7c23a6161f
2. De payment provider schotelt de gebruiker een bankkeuze voor en faciliteert de transactie.
3. De payment provider redirect de gebruiker weer naar de voorgeconfigureerde “success URL” van de webshop om te laten weten dat de betaling geslaagd is. Zo’n URL ziet er bijvoorbeeld zo uit:
https://shop/success?o=5432&sig=9f0408f22d08654d2311cbca7b60f003a9a558e7
4. De webshop controleert de signature en verwerkt de betaling.
So far, so good. Nu is er nog een extra feature: de shop mag extra parameters meegeven aan de payment provider, die bij de laatste stap weer mee terug worden gegeven. Dit is voor het gemak van de shop: die hoeft die informatie dan niet meer zelf bij te houden. Bijvoorbeeld, stel dat het framework van de shop werkt met een “OrderID” parameter in plaats van een parameter die “o” heet. Men wil verder een taal meegeven die de klant heeft geselecteerd. Dan worden de URLs:
Van shop naar payment: https://paymentprovider/?o=5432&m=1337&a=500&OrderID=5432&Lang=NL&sig=bacb96b16faa158ac9e985a59408ae7c23a6161f
Bij payment succes terug naar shop: https://shop/success?o=5432&OrderID=5432&Lang=NL&sig=f90408f22d08654d2311cbca7b60f003a9a558e7
Deze extra parameters worden echter niet meegenomen in de signature, dus aanpassing hiervan wordt niet door het systeem opgemerkt.
Wat gaat er mis?
Wat gaat er in het bovenstaande voorbeeld nu mis? Er zijn een heleboel potentiële kwetsbaarheden. Denk aan standaardinjecties in variabelen (zoals SQL injection in de OrderID parameter), maar er zit ook complexiteit in vanwege de samenwerking tussen de systemen van de shop en de payment provider. Onderstaand voorbeeld hebben we tijdens onze security tests meerdere malen aangetroffen, naast een heleboel andere business logic en interoperation-kwetsbaarheden met iDEAL-implementaties.
De shop stuurt de parameters OrderID en Lang mee, om bij terugkomst te weten om welke order het ging en welke taal de klant sprak. Het ordernummer wordt echter ook al meegestuurd in de o parameter, de parameter die de payment provider verwacht. De o parameter valt binnen het deel van de URL dat onder de signature valt, en kan dus niet onopgemerkt worden aangepast.
Niet beveiligd door signature
De integriteit van de OrderID parameter echter wordt niet beschermd door de signature, en kan dus door de gebruiker worden aangepast. Vermoedelijk heeft de ontwikkelaar deze parameter toegevoegd omdat dit de mapping naar hun ORM of framework makkelijker maakt, en er niet bij stilgestaan dat deze parameter door de gebruiker vervolgens kan worden aangepast.
Wat gaat er mis als de gebruiker die parameter aanpast? De gebruiker zou twee orders kunnen klaarzetten: eentje van €1 en eentje van €10.000. Vervolgens betaalt hij de order van €1, maar wanneer hij teruggestuurd wordt naar de Shop, verandert hij het OrderID snel in die van de €10.000 bestelling. De shop controleert de signature aan de hand van de o parameter, en die klopt, en gebruikt vervolgens de OrderID parameter om te kijken welke betaling het eigenlijk betrof. De dure bestelling wordt dus vervolgens verwerkt!
Security scanner vindt deze kwetsbaarheid niet
Zou een security scanner deze kwetsbaarheid hebben gevonden? Nee. Een stuk software heeft geen kennis van de context om bovenstaande aanval te kunnen bedenken of om ook maar vast te stellen dat de flow niet klopt wanneer je met de OrderID parameter gaat spelen. Security scanning tools lopen niet of nauwelijks flows door (ze zouden het verschil tussen security- en normale bugs niet weten). Ze proberen met het zo min mogelijk bijhouden van state specifieke soorten kwetsbaarheden te vinden. In dit voorbeeld zou het werken met tools alleen dus een serieus risico hebben opgeleverd.
Conclusie
Het is duidelijk dat alleen de inzet van tools bij security testing slechts het halve werk is. Een gedegen proces voor security testing is een combinatie van de selectie van een kundig team, de juiste werkwijze en tools. Security scanning tools (SAST en DAST tools) kunnen op de juiste plek in het proces, een hoop van het eenvoudige testwerk overnemen en al vroeg in het ontwikkelproces belangrijke feedback geven.
Voor de creatievere issues blijf je menselijk testwerk nodig hebben. Hoewel het verstandig is om van tijd tot tijd een externe check te laten doen hierop, is het natuurlijk prachtig als dit in principe door de ontwikkelaars en testers zelf gedaan wordt. Online hacking challenges en specifieke training kunnen helpen om de ervaring en expertise op te bouwen. Tot slot kan je natuurlijk functionele test cases en unit tests maken van eerder gevonden security issues en business rules!
Door Christiaan Ottow