Waarom tweestapsverificatie je niet redt (en wat wel werkt)

In het kort

Tweestapsverificatie bewijst één ding, op één moment: dat je op dat moment toegang hebt tot een tweede factor. Daarna is de vraag nooit meer aan de orde, want de server onthoudt alleen dat er iets is goedgekeurd, niet hoe. Wat overblijft is een sessiecookie, en wie dat cookie steelt, heeft je wachtwoord en je code niet meer nodig, want hij is al binnen.

Hoe een sessie werkt

HTTP heeft geen geheugen. Elk verzoek is voor de server een losse binnenkomer, dus moet er iets mee dat zegt: dit is dezelfde persoon als daarnet. Dat is het sessiecookie.

Na een geslaagde login zet de server een Set-Cookie-header in het antwoord. Je browser bewaart dat cookie en stuurt het bij elk volgend verzoek naar hetzelfde domein mee. De server kijkt in zijn sessiestore of het ID bestaat, hoeveel tijd er nog op staat en bij welke gebruiker het hoort. Klopt het, dan krijg je de pagina. Klopt het niet, dan krijg je een loginformulier.

Het punt dat de meeste uitleg overslaat: in dat proces komt je wachtwoord niet voor. Het is allang gecontroleerd en daarna weggegooid. De tweede factor is ook weg. Er is één controle over, en die heet “is dit cookie geldig”. Een sessie is dus geen bewijs van wie je bent, maar een bewering dat er ooit iemand is geweest die het mocht zijn. De server heeft geen enkele manier om te weten of de hand die nu klikt dezelfde hand is.

Moderne diensten maken het iets ingewikkelder: naast een sessiecookie is er meestal een access token met een korte levensduur en een refresh token dat langer meegaat. Dat verandert de kern niet, want al die dingen zijn dragers van dezelfde bewering. Ze zijn niet gebonden aan een persoon, maar aan een stukje opslag. Wie het kan kopiëren, kan het gebruiken. Daarom heet dit soort tokens in de literatuur bearer tokens: de drager heeft de rechten.

Er zijn pogingen om dat te repareren door een token aan een IP-adres te binden. Dat werkt slecht, want telefoons wisselen van netwerk en laptops gaan dicht en open. Dus bindt bijna niemand het. Het gevolg is precies wat je verwacht: één gestolen cookie werkt overal.

Dat is ook het verschil tussen “ben je ingelogd” en “ben je wie je zegt”. De eerste vraag beantwoordt de server met ja. De tweede kan hij niet beantwoorden, want die informatie heeft hij nooit gehad.

De drie aanvalsroutes

Er zijn drie manieren waarop een aanvaller aan dat cookie komt, en ze hebben niets met je code te maken.

Een reverse proxy tussen jou en de echte pagina

De aanvallersvariant die het meeste lawaai maakt, is de adversary-in-the-middle. Hij werkt met een domein dat op het echte lijkt, en op dat domein staat niets: het is een doorgeefluik. Bezoek je de pagina, dan haalt de server achter dat doorgeefluik de echte inlogpagina op, plakt zijn eigen adres erop en stuurt hem naar jou. Wat jij invult, gaat naar hem en hij schuift het door. Wat de dienst terugstuurt, gaat naar hem en hij schuift het door.

Voor jou is er geen enkel technisch signaal dat er iets mis is. Het certificaat is geldig, de pagina is echt, de foutmeldingen zijn echt. Je zit alleen niet rechtstreeks op de lijn. En in dat doorgeefluik wordt alles zichtbaar, inclusief het antwoord met het sessiecookie dat je na een geslaagde login krijgt. Zodra jij klaar bent, laadt hij dat cookie in zijn eigen browser en is hij binnen. Zonder wachtwoord, zonder tweede factor, zonder iets te kraken.

Hoe hij je daar krijgt, is het sociale deel: een mail dat je sessie is verlopen, een gedeeld document, een QR-code in een bericht. Dat laatste is de reden dat dit het technische zusje is van onze quishing-gids: dezelfde truc, andere verpakking.

Een infostealer op je eigen machine

De tweede route is minder elegant en veel effectiever: malware die gewoon je browser leest. Cookies staan in het browserprofiel, in een databasebestand. Ze zijn versleuteld, maar de sleutel staat op dezelfde machine, onder dezelfde gebruiker. Wie jouw rechten heeft, heeft beide. Een infostealer hoeft dus niets te breken; hij kopieert het bestand en de sleutel, en stuurt de buit naar huis.

Daar komt bij dat dit soort malware vaak in één beweging meeneemt wat er verder nog staat: opgeslagen wachtwoorden, autofill-gegevens, crypto-wallets, sessietokens van alles wat je open had staan. De buit wordt als bundel verhandeld, en de koper hoeft alleen de cookies te importeren. Geen phishing, geen code, geen slachtoffer dat iets moet goedkeuren. Je hoeft alleen één keer iets te installeren, en dat is iets wat mensen dagelijks doen.

Iemand die de stap zelf uitvoert

De derde route gebruikt geen techniek maar een mens. Iemand krijgt een code en typt hem door, of keurt een pushmelding goed omdat er toch al drie binnenkwamen. Bij MFA-vermoedheid, ook wel prompt bombing genoemd, stuurt de aanvaller net zo lang aanmeldverzoeken tot de gebruiker uit irritatie op “goedkeuren” drukt. Dat werkt beter dan het zou moeten, en het werkt het best buiten kantooruren.

De variant met een telefoontje is nog steeds in gebruik: iemand van “de IT-afdeling” belt, zegt dat er iets mis is met je account, en vraagt om de code die je net per sms kreeg “om te testen”. De code is echt. Het systeem dat hem uitgaf is echt. Alleen de persoon aan de lijn is dat niet. Onze uitleg over helpdeskfraude waarbij Microsoft of je bank belt gaat over dezelfde reflex.

Aanvalsroute Hoe het cookie bij de aanvaller komt Wat er structureel tegen helpt
Reverse proxy (adversary-in-the-middle) De aanvaller zit tussen browser en dienst en leest het antwoord mee Passkey of FIDO2-sleutel: de credential is aan het domein gebonden, dus op een lookalike levert hij niets op
Infostealer op de eigen machine Malware leest het browserprofiel en de sleutel op dezelfde schijf Endpointbescherming, geen onbeheerde software, korte sessies en apparaatgebonden tokens zodat een kopie elders waardeloos is
Code doorspelen of push goedkeuren De gebruiker voert de stap zelf uit, over de verbinding van de aanvaller Nummer matching, geen codes meer accepteren als bewijs, en een cultuur waarin niemand om je code vraagt

Waarom je code niets doet

Hier zit de kern van het hele verhaal, en hij is simpeler dan hij klinkt: de code is echt, de login is echt, de dienst is echt. Alleen de verbinding is van iemand anders.

Een TOTP-code wordt berekend uit een gedeeld geheim en de tijd. De dienst controleert of de code klopt en concludeert: deze persoon heeft het geheim. Dat is waar, want jij hebt hem net ingetypt. Wat de dienst niet controleert, en ook niet kán controleren, is naar wélke pagina je hem hebt getypt. Een code van zes cijfers zegt niets over de bestemming. Er zit geen domeinnaam in, geen handtekening, geen verwijzing naar het apparaat dat hem genereerde. Daarom werkt elk codetype dat je overtikt tegen een doorgeefluik, en daarom is een code sinds de opkomst van reverse proxies geen grens meer maar een snelheidsbord.

Bij sms komt daar een tweede probleem bij: het kanaal zelf. Een telefoonnummer is geen geheim maar een administratief gegeven, en met een beetje overtuigingskracht bij een provider verhuist je nummer naar een andere simkaart. Dan komen je codes bij iemand anders binnen, en jij ziet alleen dat je “geen bereik” hebt. Los daarvan zijn sms-codes leesbaar in de melding op een vergrendeld scherm, en zijn ze net zo makkelijk door te geven als elke andere code. Sms is beter dan niets, maar het is de onderkant van de ladder.

Pushmoeheid werkt om een andere reden: een pushmelding vraagt niet om een geheim, maar om een oordeel. En een oordeel is te beïnvloeden. Wie om elf uur ’s avonds de vierde melding van de dag krijgt, leest niet meer welke applicatie er om toestemming vraagt. Hij drukt op goedkeuren om ervan af te zijn.

Wat structureel helpt

Structureel betekent hier: de maatregel moet werken ook als de gebruiker moe is, gehaast is of niet oplet. Alles wat van een oordeel op het juiste moment afhangt, is een snelheidsbord.

Passkeys en FIDO2-sleutels: de credential zit aan het domein vast

Dit is de enige maatregel die de proxyroute in één klap sloopt, en de reden is technisch en niet commercieel. Bij WebAuthn maakt je authenticator een sleutelpaar aan dat aan een bepaald domein is gebonden. Bij het inloggen ondertekent hij een uitdaging van de server, en je browser controleert eerst of het domein in de adresbalk overeenkomt met het domein waar de sleutel voor is aangemaakt. Klopt dat niet, dan gebeurt er niets. De privésleutel verlaat het apparaat nooit, dus er valt ook niets te kopiëren.

Praktisch gevolg: een passkey voor microsoft.com doet niets op een lookalike. De proxy mag de echte pagina nog zo netjes doorgeven, de browser van het slachtoffer weigert de sleutel te gebruiken omdat het domein niet klopt. Dat is precies de laag die ontbrak bij codes.

Voor beheerders is er een tweede voordeel dat vaak vergeten wordt: je kunt passkeys verplicht stellen en de wachtwoordroute dichtzetten. Zolang er een “liever mijn wachtwoord gebruiken”-link bestaat, is de sterkte van je authenticatie die van de zwakste weg ernaartoe.

Nummer matching

Bij nummer matching toont de inlogpagina een getal van twee cijfers, en moet je dat in de app overtypen. Het is een kleine ingreep met onevenredig effect: blind goedkeuren kan niet meer, en de aanvaller moet het getal weten dat op jouw scherm staat. Dat haalt niet alles weg, want een doorgeefluik kan dat getal net zo goed doorgeven als een code. Maar het haalt de drukvariant weg, en dat is de variant die mensen zonder nadenken raakt.

Korte sessieduur en absolute timeout

Een sessie die een maand meegaat, is een maand lang een bruikbare sleutel. Twee knoppen helpen: een idle-timeout die een sessie sluit na een periode zonder activiteit, en een absolute timeout die een sessie hoe dan ook beëindigt, ook als je de hele dag hebt gewerkt. Zet “aangemeld blijven” uit op beheerde apparaten, en zorg dat refresh tokens niet eindeloos verlengd worden. Het verschil tussen een dag en een maand is het verschil tussen een incident en een hoofdstuk in een auditrapport.

Apparaatgebonden sessies en conditional access

Als je weet dat een sessie alleen vanaf een beheerd apparaat mag werken, wordt een gekopieerd cookie ineens minder waard. Conditional access is de plek waar je dat afdwingt: eisen dat het apparaat compliant of hybrid-joined is, oude authenticatieprotocollen blokkeren die geen enkele vorm van apparaatcontrole kennen, en waar je omgeving het ondersteunt sessietokens aan de apparaatsleutel binden zodat een token buiten dat apparaat niet geldig is.

Dat laatste is geen wondermiddel, want het werkt per toepassing en niet overal. Maar het verandert de rekensom van de aanvaller: een cookie uit een logbestand is dan niet meer genoeg.

Alle sessies intrekken als standaardantwoord

Als iemand ook maar denkt dat er iets mis is, is het eerste antwoord niet “wachtwoord wijzigen” maar “sessies intrekken”. Een wachtwoordwijziging raakt bestaande sessies vaak niet, en dat is precies de fout die je in incidenten het vaakst terugziet: het wachtwoord is veranderd, de aanvaller zit er nog. Trek alle sessies en refresh tokens in, verwijder app-wachtwoorden, en registreer de tweede factor opnieuw. Dat laatste is niet optioneel, want een aanvaller die binnen was, heeft vaak al een eigen methode toegevoegd.

Maatregel Waartegen Wanneer het helpt
Passkey of FIDO2-sleutel, wachtwoordroute dicht Reverse proxy, lookalike-domeinen, codes doorgeven Altijd, en vooral waar de gevolgen groot zijn: beheerders, finance, mail
Nummer matching bij push Blind goedkeuren en pushmoeheid Zolang je push gebruikt; het is een pleister, geen slot
Korte sessieduur met absolute timeout Een gestolen cookie dat dagen of weken meegaat Overal waar de dienst het ondersteunt, ook op persoonlijke accounts
Apparaatgebonden sessies en conditional access Hergebruik van een token buiten het beheerde apparaat In organisaties met apparaatbeheer; niet zinvol voor losse consumentenaccounts
Alle sessies intrekken bij twijfel Een aanvaller die al binnen is en meeLEEST Altijd, als eerste stap, vóór wachtwoord en alles daarna
Wachtwoordmanager met unieke wachtwoorden Hergebruik dat één lek in drie accounts verandert Altijd; zie een wachtwoord dat bijna niet te kraken is

Wat je als blue team ziet en doet

Het lastigste aan deze aanvallen is dat er niets kapot is. Geen malware die alarm slaat, geen geblokkeerde download, geen gebruiker die belt. Er is een sessie die doet wat een sessie hoort te doen.

Het verschil tussen een vreemd IP en een nieuwe sessie

Dit onderscheid is het halve werk. Een echte login vanaf een onbekend adres levert een aanmeldgebeurtenis op: een record met tijdstip, IP, apparaat, locatie en de uitkomst van de tweede factor. Dat record kun je filteren, en de kans is groot dat er een MFA-uitdaging in staat die de gebruiker zelf heeft goedgekeurd.

Een hergebruikt token ziet er anders uit. Er is activiteit, er worden mail gelezen en bestanden geopend, maar er is geen bijbehorende login. In de logboeken verschijnt geen nieuwe aanmelding, of je ziet dezelfde sessie-identificatie op twee verschillende adressen binnen een tijdspanne waarin geen mens kan reizen. Dát is het signaal. “Ingelogd vanaf een vreemd IP” is verdacht; “actief zonder dat er is ingelogd” is bijna een bevestiging.

Microsoft 365

  • Aanmeldlogboeken in Entra: filter op geslaagde aanmeldingen waarin de tweede factor is afgerond, en kijk naar de conditional access-uitkomst. Let op aanmeldingen met een onbekende apparaat-ID of een applicatie die je niet kent.
  • Risk detecties: “unfamiliar sign-in properties” en “impossible travel” zijn bruikbaar als aanwijzing, niet als alarm. VPN’s, reizen en mobiele netwerken maken ze ruis.
  • Nieuwe apparaatregistraties: een apparaat dat zich inschrijft vlak na een verdachte aanmelding is een klassieke vervolgstap.
  • Nieuwe authenticatiemethoden: het toevoegen van een authenticator of telefoonnummer aan een account is vaak de eerste handeling van een aanvaller. Zet daar een alert op.
  • Postvakregels: regels die post doorsturen of verplaatsen naar een map die niemand opent. Controleer met de regel-cmdlets op doorstuuradressen, ook op postbusniveau.
  • OAuth-toestemmingen: een nieuwe enterprise-app met mail- of bestandsrechten, toegestaan door een gebruiker. Dat is een blijvende toegang die geen wachtwoord en geen code nodig heeft.
  • Postbusaudit: als die aanstaat, zie je leesacties op mail zonder dat daar een aanmelding tegenover staat. Dat is het scherpste bewijs dat een sessie is hergebruikt.

Google Workspace

  • Aanmeldingsaudit in de Beheerdersconsole: kijk naar aanmeldingen met een ongebruikelijke locatie of apparaat, en naar de “suspicious login”-markeringen. De categorieën zijn minder fijnmazig dan bij Microsoft, dus combineer ze met de rest.
  • Token-audit: hier staan OAuth-toestemmingen en de bijbehorende scopes. Een app die offlinetoegang tot mail vraagt, is een langdurige sleutel. Controleer wie hem heeft gegeven.
  • Apparaatlijst: nieuwe toestellen en nieuwe inschrijvingen in mobiel beheer.
  • Gmail-instellingen: doorgestuurde adressen en nieuwe filters. In het auditlog zie je de wijziging; in het postvak zie je hem niet.
  • Sessiebeleid: stel de sessieduur in en gebruik de knop om iemand overal uit te loggen. Dat is dezelfde maatregel als bij Microsoft, met een andere naam.
Signaal Waar je het ziet Waarom het opvalt
Activiteit zonder aanmelding Postbusaudit, logboeken van de dienst Een sessie wordt gebruikt zonder dat er iemand is ingelogd
Nieuwe tweede factor op een bestaand account Auditlog van de authenticatiemethode Vaak de eerste stap na overname
Regel of filter dat post doorstuurt Beheerdersconsole, auditlog Aanvallers lezen mee zonder in te loggen
Nieuwe OAuth-app met mailrechten Token- of toestemmingsaudit Toegang die een wachtwoordwijziging overleeft
Zelfde sessie op twee netwerken Aanmeld- en activiteitenlogboeken Onmogelijk voor één persoon, tenzij het token gekopieerd is

Als er iets raak is, is de volgorde niet onderhandelbaar: sessies intrekken, tweede factor resetten, regels en OAuth-toestemmingen nalopen, en pas daarna uitzoeken hoe het begon. De eerste hulp bij hacks heeft die volgorde per dienst uitgewerkt, en account gehackt: wat nu gaat in op wat je doet als je er zelf in zit.

Het eerlijke verhaal over risico

Passkeys zijn de beste maatregel die er is, en ze zijn niet onkwetsbaar. Wie dat wel beweert, verkoopt iets.

De eerste zwakke plek is de terugvaloptie. Bijna elke dienst die passkeys ondersteunt, laat ook het wachtwoord staan, vaak met een link als “liever je wachtwoord gebruiken”. Zolang die link bestaat, hoef je alleen de zwakke deur te vinden. Voor beheerders is dat een beleidsvraag, voor consumenten is het een kwestie van zelf de link niet gebruiken en hopen dat de dienst hem ooit weghaalt.

De tweede is accountherstel. Als je je passkey kwijt bent, moet er een weg terug zijn, en die weg is per definitie zwakker dan de voorkant. Een helpdesk die een account reset na een paar vragen, is een omweg om de beveiliging heen. Wie het herstelproces niet net zo streng maakt als de login, heeft de deur dichtgedaan en het raam open laten staan.

De derde is apparaatdiefstal. Een gesynchroniseerde passkey zit in je sleutelbos van de cloud, en die sleutelbos opent met je schermvergrendeling. Een telefoon die ontgrendeld wordt gestolen, is een telefoon met je passkeys erin. Hardwaretokens zijn daar beter tegen bestand, maar die kun je verliezen, dus heb je er twee nodig, op twee plekken.

De vierde is de keerzijde van synchronisatie. Passkeys die via je cloudaccount meereizen, verplaatsen het vertrouwen naar dat account. De sleutels zijn end-to-end versleuteld, dus de provider kan ze niet lezen, maar wie jouw cloudaccount overneemt, kan wel een nieuw apparaat toevoegen en de passkeys daar laten verschijnen. Je hebt je aanmelding bij Microsoft dus deels gedelegeerd aan je aanmelding bij je telefoonfabrikant.

En de vijfde, de belangrijkste: passkeys verslaan phishing, niet malware. Als er iets op je machine draait dat met jouw rechten meeleest, kan het na je login nog steeds het sessiecookie pakken. De credential is dan onbruikbaar voor de aanvaller, maar de sessie is dat niet. Daarom staan korte sessies en apparaatgebonden tokens in dezelfde lijst als passkeys: ze lossen verschillende helften van hetzelfde probleem op.

Wat overblijft is een realistische conclusie. Je kunt niet voorkomen dat iemand ooit een keer op de verkeerde link klikt. Je kunt wel zorgen dat het daarna zo weinig mogelijk waard is: een credential die niet te kopiëren is, een sessie die snel verloopt, en een omgeving die opvalt als er iemand meekijkt.

Kort samengevat

  1. Je tweede factor beschermt de login, niet de sessie. Daarna bepaalt één cookie wat je mag, en dat cookie is te kopiëren.
  2. De drie routes zijn een doorgeefluik, malware op je eigen machine, en een mens die de stap zelf zet. Ze hebben gemeen dat je code nooit is gekraakt.
  3. Codes zijn niet aan een domein gebonden en zeggen niets over de bestemming. Daarom werken ze niet tegen een proxy, en is sms de zwakste variant.
  4. Passkeys en FIDO2 zijn de enige maatregel die de proxyroute technisch sloopt. Zet de wachtwoordroute dicht, anders is de winst theoretisch.
  5. Sessies intrekken is het standaardantwoord bij twijfel. Wachtwoord wijzigen alleen is niet genoeg, en dat is de fout die incidenten laat sudderen.

Verder lezen