0%

Wat er mis kan gaan

Bezittingen, aanvallers en vertrouwensgrenzen

Elke test die je in with Python schreef, stelt dezelfde soort vraag: krijgt het juiste verzoek het juiste antwoord? Een login met het juiste wachtwoord krijgt een . Een schrijfactie met een goed token verandert een boek. Beveiliging vraagt naar alle andere verzoeken, en naar mensen die er nooit een sturen.

Om dat ordelijk te vragen heb je drie nodig: wat de dienst bewaart dat iemand zou willen hebben, wie die iemand zou kunnen zijn, en waar hun data binnenkomt.

Wat het waard is om te pakken of te veranderen

Alles wat de leeslijst weet, staat in state. Die heeft vier delen die hier tellen, en een aanvaller wil van elk iets anders. Dit blok gebruikt een kleine kopie van de state:

Try it

De boeken zijn de reden dat de dienst bestaat, en de schade daar is een verandering: een boek dat bewerkt of verwijderd wordt door iemand die dat niet zou mogen. De andere drie zijn anders. Daar begint de schade bij het lezen, omdat elk ervan bruikbaar is zodra iemand het heeft. Een wachtwoord logt in. Een token schrijft. Een sessie-id is een login die al gebeurd is. Er een veranderen zou ook schade zijn, maar geen enkele route op deze server verandert een wachtwoord of een token, dus lezen is waar een aanvaller begint.

Wat een dienst bewaart en het beschermen waard is, heet zijn bezit (asset). Er is een vijfde dat helemaal niet in state staat: dat de dienst er is wanneer zijn gebruikers hem nodig hebben. Hoofdstuk 6 komt daarop terug.

Wie er vraagt

Over een aanvaller kun je makkelijker redeneren als je hem beschrijft naar wat hij kan bereiken, niet naar hoe slim hij is. Drie zijn genoeg voor deze cursus.

Een vreemde op het netwerk heeft geen account. Hij kan willekeurige bytes naar elke route sturen, zo vaak als hij wil, en hij ziet de antwoorden. Als de dienst over gewoon HTTP draait en de vreemde ergens op het pad tussen een gebruiker en de server zit, kan hij ook lezen wat die gebruiker stuurt. Networks with Python liet die waarnemer op het pad zien, en als het antwoord. Hoofdstuk 10 komt erop terug.

Een ingelogde lezer heeft een account. Denk aan ben: hij heeft een geldige sessie en een geldig token, en alles wat de routes hem bieden, mag hij gebruiken. Hij wordt een aanvaller als hij iets wil wat van ana is.

Iemand met een kopie van de data praat nooit met de server. Hij heeft een back-upbestand, een dump van de state, of een laptop die in de trein is blijven liggen. Hij leest state rechtstreeks.

Iemand vindt een oud back-upbestand van de data van de leeslijst. Welke aanvaller is dat?

Waar data een grens overschrijdt

Bekijk handle(request, state) nu met die aanvallers in gedachten. De twee komen van twee heel verschillende plaatsen.

Alles in request is geschreven door een client: de , het doel, elke header, de body. De server heeft er geen enkele byte van gekozen. Alles in state is van de server zelf. De lijn tussen die twee is een vertrouwensgrens (trust boundary): een plek waar data van ergens waar jij geen controle over hebt, naar ergens gaat waar je dat wel hebt.

         outside                |            inside
                                |
 client --- request ----------> |  handle(request, state) <--> state
        <-- response ---------- |
                                |
                       the trust boundary

De client en het netwerk zitten aan de buitenkant, en state zit aan de binnenkant. Een verzoek gaat de grens naar binnen over. De adapter maakt van zijn bytes de request, read_body() leest de body binnen zijn groottelimiet, en handle() beslist de rest.

Hier is het deel van handle() dat één boek serveert, ingekort:

elif len(segments) == 2 and segments[0] == "books":
    book_id = book_id_from(segments[1])
    refusal = None
    if method == "PUT" or method == "DELETE":
        refusal = write_error(request, state)
    ...
    elif method == "PUT":
        response = book_request_error(request)
        if response is None:
            response = replace_book(request, state, book_id)

Welke van deze namen bevatten data van buiten? segments wel, want die is uit het doel van het verzoek geknipt. book_id ook, want die is van een segment gemaakt. write_error leest request["headers"], en replace_book leest de -body: allebei data van buiten. state["books"] is de enige data van binnen die je ziet. Je server gaat hier al voorzichtig om met de data van buiten. Hij controleert dat het token bekend is, dat het segment een getal is en dat de body een geldig boek is, allemaal voordat hij iets verandert. Die gewoonte heeft nu een naam: data van buiten wordt bij de grens gecontroleerd voordat ze vertrouwd wordt.

Welke waarde in handle() is de vertrouwensgrens overgegaan?

De tweede oversteek

Data gaat de grens ook in de andere richting over. Een antwoord brengt data van binnen naar buiten, naar een client. Een regel in een log doet dat ook, en een back-up ook. Elk daarvan is een kopie van iets wat de server wist, en ligt nu op een plek waar de server geen controle over heeft. Les 4 bekijkt wat een back-up van deze dienst prijsgeeft.

Daar woont de derde aanvaller. Elke controle in handle() draait als er een verzoek binnenkomt. De persoon met een kopie van de data stuurt geen verzoek, dus geen van die controles draait ooit voor hem.

Waarom kan geen enkele controle in handle() de aanvaller met een kopie van de data tegenhouden?

Een dreigingsmodel

Je hebt nu drie korte lijsten: de bezittingen, de aanvallers met wat elk kan bereiken, en de grens met zijn twee oversteken. Samen vormen ze een dreigingsmodel (threat model) van de leeslijst. Dit model is met opzet klein en eenvoudig. Het moet opgeschreven worden, zodat je er een zwakke plek tegenover kunt zetten: deze aanvaller bereikt over deze grens dit bezit.

De volgende les doet precies dat, en maakt van een vage zorg iets wat je kunt oplossen.