Wachtwoorden: kiezen en opslaan
Veilige wachtwoorden
Ongeacht de veiligheden die we inbouwen als cyberboswachter, veel blijft afhangen van de manier waarop eindgebruikers omgaan met hun wachtwoorden. Volgende regels worden continu niét gehanteerd, met alle gevolgen van dien:
- Hergebruik nooit een wachtwoord. In principe heb je één wachtwoord pér service (applicatie, website, etc.).
- Gebruik geen wachtwoorden die in dictionaries staan. Maar overweeg volledig random gegenereerde wachtwoorden.
- Zorg ervoor dat je wachtwoorden lang genoeg zijn (minimum 16 tekens).
- Zorg ervoor dat je wachtwoorden steeds een combinatie van cijfers, letters (grote én kleine) en leestekens zijn.
Trouwens, herinner je je de McCumber kubus waarin we benadrukten dat technologie maar één aspect is om CIA toe te passen op je data in z’n drie primaire vormen? Het zal je niet verbazen dat cybercriminelen niet altijd gaan proberen databanken aan te vallen om wachtwoorden van gebruikers te pakken te krijgen. Als zij een specifiek doelwit hebben dan gaan ze vaak op andere manieren te werk:
- Password spraying: hierbij gaat de hacker een (beperkte) lijst van veelgebruikte wachtwoorden testen op een grote groep useraccounts van een bepaalde website, in de hoop een hit te hebben (“spray and pray”).
- (Spear) phishing: bij phishing hanteert de aanvaller de goedgelovigheid of onoplettendheid van de gebruiker om een ogenschijnlijk betrouwbare mail of bericht te sturen met daarin een link naar een pagina die malware installeert of een fake login scherm toont. Bij spear phishing gebruikt de aanvaller geen massmail, maar gaat hij juist gericht één specifiek doelwit een op maat gemaakte mail of bericht sturen. Spear phishing is heden ten dage één van dé social engineering aanvallen bij uitstek.
- Keyloggers: als de aanvaller toegang heeft tot de computer (wat uiteraard van over het netwerk kan) dan kan hij een (permanente) keylogger installeren die alle toetsaanslagen op het systeem opneemt. Nadien kan de aanvaller deze logs dan analyseren in de hoop zo ook het wachtwoord of andere gevoelige informatie terug te vinden.
Hoe wachtwoorden opslaan
De aanvallen die we zonet besproken hebben (spraying, phishing, keyloggers) zijn hoofdzakelijk online aanvallen: de aanvaller probeert actief in te loggen of onderschept wachtwoorden bij de bron. Daarnaast bestaan er offline aanvallen, waarbij de aanvaller (via een SQL injection, een insider of een datalek) (leestoegang tot) de gebruikersdatabank heeft bemachtigd. Vanaf nu plaatsen we ons in de schoenen van de beheerder en gaan we uit van het ergste: ga er van uit dat jouw databank vroeg of laat zal lekken. Hoe moet je dan als cyberboswachter de login gegevens van je gebruikers bewaren zodat zo’n lek minimale schade oplevert? We gaan een soort bottom-up aanpak hanteren, waarbij we beginnen met de meest naïeve oplossing en telkens verbeteringen zullen aanbrengen.
Paswoorden als plaintext
In het prille begin van het Internet gebeurde dit quasi overal: de login-databank had twee kolommen:
- gebruikersnaam.
- gebruikerswachtwoord.
De wachtwoorden in kolom 2 stonden er zoals ze waren. Als een gebruiker wilde inloggen op dit soort websites dan moest hij z’n wachtwoord verzenden en dan ging de backend controleren of het ingezonden wachtwoord overeen kwam met het wachtwoord in de database. Het spreekt voor zich dat dit soort databanken van gigantische waarde zijn voor aanvallers: van zodra ze de databank hebben te pakken hebben ze alle wachtwoorden van alle gebruikers! Profit!
Paswoorden mogen nooit in onbeveiligde, leesbare vorm in een databank staan! Wanneer dit wel zo is dan kan je beter ogenblikkelijk je account bij die service deleten. Want alhoewel deze aanpak al lang bestaat en er al bijna even lang van geweten is dat deze erg onveilig is, toch zijn er nog steeds ontelbare websites en applicaties die hieraan zondigen. Als ze dus jouw wachtwoord zo behandelen, dan is de kans reëel dat ook hun andere veiligheidsdiensten niet om over naar huis te schrijven zijn.
Een goede manier om te weten of een service op deze manier werkt is gebruik maken van de “Ik ben m’n wachtwoord vergeten”-knop. Als je deze knop gebruikt en je krijgt een e-mail met daarin jouw originele wachtwoord, dan kan je er zeker van zijn dat de service jouw wachtwoord op deze manier bewaart. In principe zou een service NOOIT jouw wachtwoord moeten kunnen zien. We gaan zelfs zien dat jouw wachtwoord nooit je computer mag verlaten, laat staan dat deze beschikbaar is als plaintext in een database.
Wie zou nu zo dom zijn? In 2019 onthulde Facebook dat het jarenlang wachtwoorden van honderden miljoenen gebruikers (schattingen tussen 200 en 600 miljoen) gewoon in plaintext had bewaard. De bestanden waren toegankelijk voor zo’n 20.000 Facebook-medewerkers. Zelfs de grootste techbedrijven vallen dus soms in deze klassieke val. Meer lezen: theverge.com.
Paswoord hashing
Door een wachtwoord te hashen kunnen we de wachtwoorden al iets veiliger bewaren. Een gebruiker die zich wenst aan te melden bij een systeem dat met wachtwoord hashes werkt zal nu op zijn lokale systeem z’n hash moeten genereren en dit over het netwerk doorsturen. De service zal deze ontvangen hash vergelijken met de waarde die in de database staat, en indien deze gelijk is dan wordt verondersteld dat de gebruiker het juiste wachtwoord kende.
We versturen dus niet meer het wachtwoord over het netwerk, maar we zijn nu wel vatbaar voor een pass-the-hash aanval. Het volstaat om een geldige combinatie van gebruikersnaam en hash te capteren en deze vervolgens te gebruiken om ergens in te loggen. De aanvaller heeft hierbij geen kennis nodig van het originele wachtwoord.
Door een secure hash van een wachtwoord te genereren creëren we een stuk tekst dat niet terug naar het originele wachtwoord kan omgezet worden. In theorie zal ieder wachtwoord een andere hash creëren. Uiteraard kunnen er toch twee zaken zich voordoen:
- Twee totaal verschillende wachtwoorden genereren dezelfde hash, een zogenaamde collision.
- Twee mensen kiezen hetzelfde wachtwoord en zullen dus ook dezelfde hash genereren.
Dit probleem gaan we verderop oplossen met behulp van salting.
Veel websites genereren wel degelijk de hash aan serverzijde. Het verschil hier is echter dat ze eerst een beveiligde TLS tunnel hebben opgezet waarover het wachtwoord werd verstuurd. Het laat echter de website/service toe om meer controle te hebben over de hash-generatie.
Rainbow table attack
Als de database door aanvallers gestolen wordt dan zitten we ook een tikkeltje veiliger als voorheen (de pass-the-hash aanval zal uiteraard nu zeker werken) indien de aanvaller de wachtwoorden van gebruikers nodig heeft (om bijvoorbeeld vervolgens op een ander systeem te gebruiken). De aanvaller zal een bruteforce of dictionary attack moeten uitvoeren om te ontdekken welk wachtwoord resulteert in welke hash. Dit kan een erg tijdrovend proces zijn want enkele veelgebruikte hashing algoritmen (onder andere scrypt, bcrypt en pbkdf2) zijn by design zodanig geschreven dat deze erg traag werken. Dit zorgt ervoor dat de tijd om één hash te genereren geen voelbaar verschil geeft, maar wanneer een aanvaller er duizenden per seconden wil kunnen testen, dan zal het algoritme als een stevige timebottleneck optreden. De aanvaller zou dan in de plaats vooraf alle hashes kunnen precomputen als alternatief. Dit heeft dan weer voor gevolg dat zo’n lijst gigantisch groot is en er dus een memorybottleneck optreedt.
Even concreet. Stel dat gebruikers enkel wachtwoorden van 8 kleine letters mogen kiezen. Dat geeft 26⁸ ≈ 2·10¹¹ mogelijke combinaties.
- Bij 1 miljoen hashes per seconde kost het gemiddeld 2,4 dagen om de volledige ruimte te doorzoeken - nog te overzien, maar met een moderne GPU gaat het veel sneller (commerciële tools haalden in 2011 al 2,8 miljard wachtwoordpogingen per seconde).
- De alternatieve piste (álle hashes vooraf precomputen) vraagt ~1,46 TB opslag, en dat is nog enkel voor deze beperkte set van 8 kleine letters. Ter vergelijking: alle Google-servers tezamen bewaren in de orde van 10¹⁵ bytes (1 petabyte).
Conclusie: puur bruteforcen of puur precomputen loont nauwelijks. Rainbow tables zoeken bewust een compromis tussen beide uitersten.
De aanvaller zit dus met het dilemma (tijd versus geheugen) tussen hashen berekenen ter plekke, wat erg traag zal gaan, oftewel alle mogelijke hashes op voorhand berekenen, wat veel geheugenplek vereist. Via een rainbow table attack krijgt de aanvaller echter een handig instrument in handen dat een compromis tussen beide bottlenecks aanbiedt.
Een rainbow table is een tabel van precomputed hashes, maar waarvan we ze niet allemaal moeten bewaren om toch over een grotere set te beschikken dan die dat in de tabel bewaard worden. Je zou het kunnen vergelijken met een gecomprimeerde lijst van de getallen van 1 tot en met 101, waarbij we enkel het start (1) en eindgetal (101) bewaren, en dan erbij zeggen dat ieder volgend getal het vorige +2 is.
Een rainbow table stel je als volgt op:
- Je kiest een startpunt, zijnde één van de mogelijk wachtwoorden uit de set van wachtwoorden waarvoor je een rainbow table wilt opstellen (zie figuur hier voor)
- Je genereert de hash van dit gekozen wachtwoord.
- Je past nu op deze hash een reduction functie toe. Dit is een zelfgekozen mapping van de verkregen hash terug naar een wachtwoord uit de set van mogelijk wachtwoorden.
Als reductiefunctie zou je bijvoorbeeld kunnen beslissen om de hash om te zetten naar een getal (bv. door de som van de ASCII-waarden van de letters van de hash te nemen) en dit getal dan te gebruiken als index die bepaalt welk wachtwoord je uit de wachtwoordenset gaat kiezen.
- Van dit nieuwe wachtwoord genereer je weer een hash en pas je opnieuw de reduction functie toe.
- Die combinatie hash+reductie blijf je X aantal keer herhalen tot je een lange lijst hebt (een zogenaamde chain van bijvoorbeeld 10000 elementen).
- Finaal hou je nu van deze lijst enkel het startpunt bij (het gekozen wachtwoord uit de set) en de allerlaatste gegenereerde hash.
Wanneer de aanvaller nu van een gestolen hash terug het wachtwoord te pakken wil krijgen dan zal hij:
- Deze hash als startpunt gebruiken en hier telkens weer de combinatie reductie+hash op toepassen.
- Totdat een hash wordt gevonden die als eindpunt voor een van de lijsten werd ingesteld.
- De aanvaller neemt vervolgens het startpunt van deze lijst (een wachtwoord) en past daarop opnieuw de combinatie van hashing en reductie toe (als het ware opnieuw een rainbow table genereren). Uiteindelijk zal hij weer op de gestolen hash uitkomen.
- Als hij dan één stapje terug kijkt dan zal hij daar het wachtwoord zien die bij deze gestolen hash hoort.
Salting
Om bestand te zijn tegen de rainbow attack dienen we de set van mogelijke wachtwoorden gevoelig te vergroten waardoor het niet meer realistisch is om voor die set rainbow tables te genereren. We kunnen helaas niet verwachten van de eindgebruikers dat zij met véél langere, meer willekeurige, wachtwoorden op de proppen komen en zullen dus een ‘oude’ truc moeten gebruiken die we ook al bij wifi hebben gezien. Bij wifi hanteerden we een initialisatie vector (IV) om de WEP-sleutel met 24 bits te verlengen zodat zelfs bij dezelfde sleutel, iedere IV eigenlijk zorgt voor een unieke seed.
Wel nu, dit concept kan je ook toepassen bij wachtwoorden en heet salting. Een salt is een extra stuk dat je toevoegt aan het wachtwoord voor je de hash berekent. Dit extra stukje is een willekeurig getal dat je uiteraard mee zal moeten opslaan in de database. Wanneer twee gebruikers hetzelfde wachtwoord zouden hebben, dan zouden ze (dankzij hun unieke salt) toch beiden totaal verschillende hashes genereren. Niet alleen dat, maar de salt zorgt er dus ook voor dat de set van mogelijk wachtwoorden véél groter wordt.
In de database bewaren we nu volgende informatie:
- Gebruikersnaam.
- Gebruikte salt (minimum 32 bits).
- Hash van het wachtwoord en de salt samen.
Merk op dat ook nu we nog steeds niet beschermd zijn tegen pass-the-hash aanvallen.
Mimikatz
Mimikatz werd origineel ontwikkeld als demo om aan te tonen dat de authenticatieprotocollen van Microsoft onveilig waren. Helaas is de tool totaal erg snel opgenomen in het arsenaal van de digitale stropers. De tool laat toe om authentication tickets te tonen en hergebruiken. Zo’n authentication tickets worden door de loginserver aangemaakt na een geslaagde loginfase door de gebruiker. Dit ticket kan de gebruiker dan aan een systeem aanbieden om toegang tot het systeem te krijgen (het is letterlijk een toegangsticketje). Wanneer Mimikatz wordt losgelaten op een Microsoft Windows besturingssysteem zal het deze tickets op het systeem zoeken zodat de aanvaller vervolgens zonder login gegevens toch kan inloggen door technieken zoals:
- Pass-the-hash: vroeger werden Windows wachtwoorden als hash (NTLM) bewaard op het systeem waardoor deze techniek erg eenvoudig was.
- Pass-the-ticket: zoals zonet beschreven, maar dan met het Kerberos ticket (zie ook hierna)
- Kerberos Golden Ticket: Kerberos is een van de meest gebruikte authenticatieprotocollen. Veel systemen die Kerberos gebruiken hebben echter een verborgen account (KRBTGT genaamd) wiens ticket domain admin rechten geeft én dat niet vervalt. Kortom, een gouden ticket!
- Pass-the-cash: identiek aan pass-the-ticket maar deze aanval werkt ook met logindata die zowel op Mac, Unix én Linux kan worden gevonden. Kortom, dit is natuurlijk de motherload, daar deze niet meer afhankelijk is van enkel Microsoft Windows besturingssystemen.
Het nadeel van Mimikatz, voor ons als boswachters, is dat de tool erg goed werkt én kan geautomatiseerd worden. In 2017 onderging Oekraïne een stevige ransomware aanval van (zo goed als zeker) Russische makkelij, genaamd NotPetya (een variant op de WannaCry ransomware). NotPetya gebruikte een aangepaste versie van Mimikatz zodat de ransomware zichzelf kon verspreiden over het netwerk en op andere systemen in het domein kon inloggen met hashes en tickets dat de Mimikatz variant aantrof.
De oorsprong van Petya en NotPetya werd getraceerd en is vermoedelijk het resultaat van een Russische hackinggroep genaamd Sandworm die onder de GRU werken, de Russische militaire inlichtingendienst.
CRAM en SCRAM
Om iemand te authenticeren spraken we tot nog toe enkel over een username/wachtwoord systeem. Echter, er zijn vele andere manieren om iemand te authenticeren. We spreken over “Challenge-Response Authentication Mechanism (CRAM) wanneer de gebruiker een vraag gesteld krijgt (de challenge) en hij hierop een geldig antwoord (de response) moet geven voor hij wordt toegelaten. Authenticeren met een wachtwoord is dus een vorm van CRAM. Er zijn er echter nog vele andere, denk maar aan de gehekelde CAPTCHA’s - de ambetante vraag om te bewijzen dat je geen robot bent door alle boten in een afbeelding aan te duiden - of inloggen met behulp van je irisscan.
Het mechanisme van een CRAM werkt als volgt:
- De gebruiker stuurt z’n username met de vraag om in te loggen.
- De server genereert een random challenge en stuurt deze terug.
- Server en client creëren nu een hash van deze challenge met de hash van het wachtwoord (de server heeft dit bewaard, de client genereert de hash door z’n wachtwoord in te voeren)
- De client stuurt deze hash, de response, terug naar de server.
- De server vergelijkt of zijn gegenereerde response hash dezelfde is als die van de gebruiker.
SCRAM (Salted Challenge-Response Authentication Mechanism) bouwt voort op CRAM met twee verbeteringen. We bespreken een vereenvoudigde versie die de kern toont; de echte SCRAM (RFC 5802) voorziet bovendien mutual authentication en gaat (zoals je hieronder zal zien) nog een belangrijke stap verder.
- De server bewaart een gesalte hash in plaats van een gewone hash, en stuurt de salt mee bij elke loginpoging. Daardoor moet de client telkens opnieuw uit wachtwoord + salt zijn hash afleiden - er is geen vaste hash die op het clienttoestel hoeft te staan.
- De gesalte hash zelf wordt nooit over het netwerk verzonden; enkel
H(pw_hash + challenge)gaat over de draad, en omdat de challenge per sessie verandert kan een afgeluisterde response niet hergebruikt worden.
Let op: ook deze vereenvoudigde SCRAM beschermt niet tegen pass-the-hash wanneer een aanvaller de gesalte hash uit de gelekte databank steelt. De aanvaller kan dan immers gewoon H(gestolen_hash + challenge) berekenen en zich aanmelden. De salting maakt wél offline brute-force en rainbow-tables een stuk duurder. De échte SCRAM lost de pass-the-hash kwestie wél op (zie callout hieronder).
RFC 5802 gebruikt een slimme asymmetrische constructie. Uit het gesalte wachtwoord leidt de client twee sleutels af: een ClientKey (waarmee de client zich bewijst) en een StoredKey = H(ClientKey) (wat de server bewaart). De server bewaart dus niet de sleutel waarmee je inlogt, maar een eenrichtingshash daarvan. Een aanvaller die StoredKey uit een gelekte databank steelt, kan er ClientKey niet uit afleiden - en zonder ClientKey is inloggen onmogelijk. Dát is wat pass-the-hash bij de échte SCRAM onmogelijk maakt.







