Secure by Design - Hoofdstuk 9

Ingediend door Dirk Hornstra op 28-sep-2020 07:16

In januari zijn we gestart met hoofdstuk 1 (link), in februari met hoofdstuk 2 (link), in april met hoofdstuk 3 (link), in mei met hoofdstuk 4 (link), in juni met hoofdstuk 5 (link), in juli met hoofdstuk 6 (link), in augustus hoofdstuk 7 (link), in september hoofdstuk 8 (link) en nu wordt het tijd voor het 9e hoofdstuk.

De Timo-samenvatting

Controleer of je excepties wel fouten zijn. Zijn het "verwachte failures", handel ze dan af met Result-objecten.
Let goed op wat je logt. Sla je een volledig Account-record met wachtwoord en salt op? Dat gaat je waarschijnlijk een data-lek opleveren.
Bouw je systemen zodat ze load kunnen verdragen. Zorg voor een circuit-breaker op plekken waar je afhankelijk bent van externe diensten. Stel time-outs in. Gebruikt Queue's. Gebruik "bulk-heads" om te zorgen dat bij problemen op locatie X in je code, je er op de andere plekken geen last van hebt.

Het punt van de externe diensten is een "goeien één". Stel dat Buckaroo er een halve dag uit zou liggen. Waarschijnlijk gaan alle onze check-outs "op hun plaat". Eigenlijk zou er "iets" moeten komen waardoor we 1. de melding tonen: er kan geen verbinding gemaakt worden met de betaalprovider. 2. de circuit-breaker zou hier ingezet moeten worden, waarom zou je kopers nog doorsturen als je al weet dat het niet gaat werken? 3. die circuit-breaker kan zo nu en dan wel mensen doorlaten om te checken of de dienst weer werkt. 4. het mooiste zou natuurlijk zijn dat je een soort dienst hebt, waarin je de bestelling tijdelijk opslaat, en de melding toont: op het moment dat de betaalprovider weer beschikbaar is, ontvang je van ons een e-mail met een link om je bestelling af te ronden. We hebben dit werkend bij een klant waarbij de volgende ochtend de mail verstuurd wordt: "er staan nog producten in je winkelwagen, klik hier om je order af te ronden".
 

De titel van hoofdstuk 9 is "Handling failures securely".

Het afhandelen van fouten heeft ook betrekking op security. Want dump je een volledige stacktrace op het scherm, wordt er meer data zichtbaar dan de bedoeling was? Of trek je ook andere systemen naar onderen als jouw systeem een fout heeft? Dit hoofdstuk moet je inzicht geven wat de implicaties van security zijn bij het maken van bepaalde ontwerp-keuzes.

Gebruik excepties om fouten af te vangen.

Excepties worden vaak gebruikt omdat het je flow "stil laat staan" en de fout teruggeeft. Deze bevatten vaak de "waarom" (je deelde een getal door nul) en "waar" (stacktrace van code, fout op regel 30, aanroep vanaf regel 12, etc.). In het voorbeeld van het boek wordt zichtbaar dat er een java SQL exceptie opgetreden is. Info die een buitenstaander weer niet hoeft te zien (want die kan gaan kijken of er met SQL injection meer te halen is e.d.). Dat dit vaak zichtbaar is wordt door het boek veroorzaakt door een "sloppy design" en het niet weten dat er excepties kunnen optreden.

Excepties opwerpen

Er zijn 3 oorzaken waardoor excepties op kunnen treden;

  • business rules violations
  • technical errors
  • fouten in het onderliggende framework


Business rule violations treden op in het domein, dus als je nog producten wilt toevoegen aan een afgeronde order, geld wilt afboeken van een rekening met onvoldoende saldo.

Technical errors zijn niet domein-gerelateerd, het zijn fouten als delen door 0 (wat niet mag) en bijvoorbeeld het niet voldoende geheugen beschikbaar hebben bij het schalen van afbeeldingen.

De schrijvers van het boek vinden dit een goede scheiding, maar volgens de auteurs zijn er mensen die het hier niet mee eens zijn en deze fouten samenvoegen, omdat het algemene doel is om fouten af te vangen/handelen. De auteurs waarschuwen dat dit een deur opent naar het bouwen van een complex systeem en het ontstaan van mogelijke beveiligingsproblemen.

We krijgen een voorbeeld van een bank-transactie. het opvragen van een account. Deze throwt een exceptie als het rekeningnummer niet gevonden kan worden en een exceptie als er een probleem met de database is. Omdat beide van het type IllegalStateException is, weet je nu eigenlijk niet of het "gewoon" een niet te vinden nummer is (bij invoer een typefout gemaakt?) of dat er een probleem met de database is.

Excepties afvangen

Een try-catch en klaar. Maar in ons voorbeeld dus niet. Want beide zijn van type IllegalStateException, hoe geef je alleen een foutmelding terug als het rekeningnummer niet gevonden kan worden? In dit geval door op de tekst te zoeken naar "No account matching". Maar dat is "vies", want als de tekst wordt aangepast, dit er niet meer in zit, dan wordt de melding niet meer getoond. In het boek wordt dus netjes een AccountException-class gemaakt die erft van de algemene Exception-class, de class AccountNotFoundException is vervolgens een afgeleide class van AccountException. Op deze manier kan het systeem de domein-gerelateerde fouten afhandelen door het afvangen van de specifieke fout van type AccountNotFoundException en de algemene AccountException. Je weet dan dat de andere fouten technische fouten zijn, die wil je loggen en het systeem terug laten gaan naar de beginstap.

Het werken met exception payload

In de exceptie werden rekeningnummer en klantgegevens meegenomen. We hebben dat later mogelijk nodig voor het uitzoeken van "de fout". Maar hierdoor komen ook het BSN-nummer e.d. in de logs. Je kunt je person-tabel superbeveiligd hebben, als dezelfde data bij fouten in een log-tabel geplaatst wordt waar iemand die deze data niet zou mogen hebben het wel kan raadplegen, dan heb je een data-lek. Bij het meenemen van data bij een fout moet je jezelf dus de vraag stellen, heb ik deze data echt nodig of kan dit voor problemen zorgen? In hoofdstuk 5 hebben we het read-once pattern besproken, waardoor je onder andere wachtwoorden slechts 1x kunt uitlezen.

Het afhandelen van fouten zonder excepties

Het gebruik van excepties bij fouten in het domein-gedeelte wordt vaak gedaan, maar ook wordt dit juist vaak zonder excepties gedaan. Excepties zijn eigenlijk zaken die je niet verwacht, terwijl "failures" zaken zijn die aangeven dat iets fout is, maar dat je het wel mogelijk had verwacht.

Het boek geeft het voorbeeld van een bank, een transactie van geld. Dit gaat goed, of gaat fout doordat er te weinig geld op de rekening staat. In het voorbeeld wordt er een "insufficientFunds"-exceptie opgeworpen als er niet genoeg geld op de rekening staat.

Maar deze actie vindt regelmatig plaats. Mensen kijken niet goed of er wel voldoende op de rekening staat of denken dat ze een tijdje rood mogen staan. Omdat het zo vaak voorkomt is het dus niet exceptioneel, maar een mogelijke uitkomt: onvoldoende saldo.

De code geeft nu een Result-object terug, waarbij dit van type failure of success kan zijn (interface ITransferResult, class TransferFailure en class TransferSuccess), waarbij TransferFailure een enumeratie bevat waarom de transactie niet gelukt is.

Ontwerpen voor beschikbaarheid

De beschikbaarheid van data en systemen is een belangrijk veiligheidsdoel en maakt deel uit van het CIA acronym: confidentiality, integrity en availability. De publicatie "Engineering Principles for Information Technology Security" van NIST (nationale instituut van standaarden en technologie) heeft het over 5: confidentiality, availability, integrity, accountability en assurance.

Veerkracht (resilience)

Systemen die veerkrachtig gebouwd worden, blijven draaien, ook bij een hoge belasting. Ze kunnen trager worden, maar als de druk weer weg is, draaien de systemen weer zoals ze horen te draaien.

Reactievermogen (responsiveness)

Als je systeem onder hoge druk staat, is het soms beter om "snel" een (fout-) melding te tonen (door drukte zijn we minder goed bereikbaar), dan dat de bezoeker 5 minuten lang naar een wit scherm zit te kijken en dan nog een melding krijgt (of geen enkele melding, omdat er time-outs opgetreden zijn). Het systeem kan technisch nog wel draaien, voor de bezoeker lijkt het alsof "het niet meer werkt".  Een mogelijke oplossing om niet op een bepaald moment alleen maar "niet beschikbaar" meldingen te geven, kun je eraan denken om een queue te gebruiken. Je zou een queue kunnen maken voor het accepteren van aanvragen en één voor het daadwerkelijke werk. Die voor het accepteren van aanvragen kan dan snel de melding geven "aanvraag ontvangen, we gaan deze verwerken". Als je verwerk-queue volloopt moet je misschien geen nieuwe requests accepteren.

Circuit breakers en time-outs

Om een veerkrachtig systeem te bouwen wordt vaak het circuit-breaker-pattern toegepast. Het werkt op dezelfde manier als de stoppen in je meterkast. Als er teveel spanning op komt, knalt de stop eruit zodat niet het hele huis afbrandt. De circuit-breaker in je systeem is dicht, alle requests komen binnen. Als er teveel fout gaan, wordt een bepaalde treshold gehaald en gaat de circuit-breaker open, hierdoor komen geen requests meer binnen. Zo nu en dan gaan we naar status "half-open" om enkele requests door te laten, zodat gecontroleerd kan worden of die wel weer goed gaan (het systeem is hersteld). Als dat goed gaat, gaat de circuit-breaker uiteindelijk weer dicht. Als deze status "open"  heeft, wordt dus eigenlijk het fail-fast-pattern toegepast.

Als je aanroepen doet naar een extern systeem moet je zorgen dat er een time-out ingesteld is. Vaak worden circuit-breakers en connecties met externe systemen gebruikt.

Als een aanroep mislukt wordt vaak een standaard antwoord terug gegeven, een fallback answer. Op basis daarvan kan de rest van de flow nog wel afgehandeld worden. De afhandeling moet vaak in samenspraak met een domein-eigenaar gemaakt worden. Als niet gecontroleerd kan worden of er voldoende voorraad is, mag de order dan wel geplaatst worden of moet deze geweigerd worden? Of bij een ander voorbeeld, als er geen boeken van een auteur opgevraagd kunnen worden, is het OK om een lege lijst te geven, of is dat niet acceptabel, omdat het voor de aanvragende partij dan niet duidelijk is dat er misschien wel boeken van deze auteur zijn, maar dat door een technisch probleem ze niet opgevraagd kunnen worden.

Schotten (bulkheads)

Het bulkheads-design-pattern wordt ook vaak gebruikt om te voorkomen dat een fout op 1 plek in het systeem zorgt voor fouten op andere plekken in het systeem. In de scheepvaart worden schotten gebruikt om delen van het schip af te sluiten als ergens een lek of brand uitbreekt. Het boek geeft een aantal voorbeelden;

Location Level

Als je servers over verschillende locaties hebt draaien, zorg je dat deze "stand-alone" kunnen draaien, geen afhankelijkheden naar de andere locatie. Als op de andere locatie door een aardbeving de servers eruit liggen, heeft dat geen impact op je servers op andere locaties.

Infrastructure Level

Je kunt de workload op een server verdelen.  We krijgen het voorbeeld van een webshop, waarbij de algemene productinformatie die getoond wordt los staat van de servers die de orderflow afhandelen. Als er problemen met de orderflow zijn, kan de "browsende klant" nog gewoon je producten bekijken. Het "partitionen van services en servers" heeft nog wel een addertje onder het gras. Als ze dezelfde database gebruiken, kunnen deadlocks ervoor zorgen dat iedereen last krijgt van mogelijke problemen op plaats X.

Code Level

Een algemeen voorbeeld van het bulkhead-pattern zijn thread-pools. Als je elk request een thread laat opstarten, zit je na een korte tijd op de max en kun je geen services meer leveren. Daarom heb je een algemene thread-pool waar je jouw objecten uit kunt halen. Queues zijn hier vaak aanvullingen op, als het werk niet opgepakt kan worden, wordt het in een queue gezet, zodat op een rustiger tijdstip dat alsnog gedaan kan worden.

Het Reactive Manifesto is een manifest wat opgesteld is voor beschikbaarheid en hier na te lezen: reactivemanifesto.org

Afhandelen van Bad Data

Het is altijd mogelijk dat je "bad data" krijgt. Hetzij door invoer van de klant, hetzij dat een netwerk-connectie wordt afgebroken en je uit een externe bron ongeldige XML terug krijgt. In hoofdstuk 4 zijn contracten besproken, om te zorgen dat de input geldig is.

Repareer data niet voor validatie

Het boek geeft een voorbeeld van een webshop, waarbij de membership-database wordt verrijkt met data uit een extern systeem. Die data is echter niet zo strikt als dat we het zouden willen hebben. In het voorbeeld wordt de eigen database gegeven, waarbij "vreemde karakters" worden gedropt, dus Henk van 't Veld wordt Henk van t Veld. Dit veld wordt verrijkt uit een ander systeem, daar komen deze karakters wel in voor en ook < en > door een mislukte XML-import. Er komt nu een "repair-filter" tussen, om te zorgen dat ook ongeldige data wordt opgeschoond en zo wordt weggeschreven in de membership-database. We krijgen het voorbeeld waarbij de < en > verwijderd worden, maar in de input is ook URL-encoded een %3C en %3E geplaatst, wat ook een kleiner- en groter-dan teken is.

Geef data niet letterlijk weer

In het voorbeeld wordt de melding letterlijk getoond en krijg je een script-alert. Dit is ter test, het geeft aan dat javascript uitgevoerd kan worden. En als je dit in een log-viewer doet kan dat weer andere problemen opleveren. Als je niet zeker weet wat je met data moet doen, kies dan voor de veilige oplossing: niet tonen.

XSS polyglot

Een XSS polyglot is een aanvals vector wat in meerdere situaties Javascript kan uitvoeren. We zien hoe deze binnen een DIV, binnen een noscript-tag en binnen een comment-tag geplaatst kan worden. Het boek geeft nog 4 linkjes voor meer informatie:

https://owasp.org/www-community/attacks/xss/

https://web.archive.org/web/20190617111911/https://polyglot.innerht.ml/

https://research.chalmers.se/publication/189673

https://www.slideshare.net/MathiasKarlsson2/polyglot-payloads-in-practice-by-avlidienbrunn-at-hackpra