This article will demonstrate one situation discovered during a recent cloud penetration test that allowed us to pivot from a Microsoft cloud environment to on-premise machines via PSRemoting.

Yes, you read the above statement correctly; we leveraged PSRemoting for lateral movement from the cloud to on-premises. We replicated the attack path in our lab environment.

Note: This article will not contain the steps for deploying the lab or replicating the lab environment. It also won’t focus on OPSEC or bypassing the PowerShell logging capabilities as it depends from environment to environment.

Lab Design

Let’s get started by first understanding the resources and components that were present and helped us in gaining command execution on the on-premise machines.

We started the penetration test with normal read access on the target tenant and found an attack path that helped us gain contributor privileges to a resource group via service principal.

Contributor Privileges Acquired

We authenticated with the service principal credentials using Az PowerShell module, listed all the resources, and found multiple resources, such as Function App, Key Vault, Azure Relay, etc.

Multiple Resources Discovered

So, let’s first discuss Function App, Azure Relay, and Hybrid Connections, and then we can continue with our attack path story.

Function Apps

Function Apps (Lambda in AWS) are serverless computing services provided by Azure Cloud. They allow developers to build and deploy small functions that can be triggered by events such as simple HTTP requests. Function Apps provide an environment for executing our code without the need for managing the underlying infrastructure, like operating system, storage, load balancers, RAM, networking, etc. They are designed to simplify the development and deployment of individual functions, abstracting away the complexities of infrastructure management and enabling developers to focus solely on writing code.

Azure Relay

The Azure Relay service provides options for organizations to expose services running on-premises in the corporate network to the public cloud. There is no need to punch a hole in the Firewall as the agent installed on the on-premise machine will initiate an outbound connection to the Azure Relay service and create a bi-directional socket for communication. When the client connects to the Azure Relay service, the data is relayed to the on-premise machine.

Hybrid Connection

Hybrid Connection is a feature in Azure Relay that is secure and leverages open-protocol evolution based on HTTP and WebSockets. It enables bi-directional, request-response, and binary stream communication and simple datagram flow between two networked applications. It allows a few services such as App Services and Function Apps to interact with other services that are hosted on-premises.

Now that we know about all three components, let’s move on with our attack path story.

When we found out about Hybrid Relay, we started enumerating the details and found that the connection is created for a machine over the 5986 port. We immediately thought of PSRemoting, as 5986 is the HTTPS port for the WSMan protocol, which is leveraged by PSRemoting.

Enumerating the Hybrid Connection

We checked if the hybrid connection was attached with the Function App. To check the configuration, we used the REST API call, as there is no built-in PowerShell cmdlet that we know of that can provide such details. To use the REST API call, we requested an access token for the “https://management.azure.com” endpoint.

Requesting Access Token

Once we had the access token, we could leverage the below PowerShell code to send the REST API call and get the hybrid connection details.

$AccessToken = "eyJ0eXAiOiJK............."
$URL = "https://management.azure.com/subscriptions/<Subcription ID>/resourceGroups/<Resource Group Name>/providers/Microsoft.Web/sites/<Function App Name>/hybridConnectionRelays?api-version=2018-11-01"

$Params = @{
    "URI"     = $URL
    "Method"  = "GET"
    "Headers" = @{
        "Authorization" = "Bearer $AccessToken"
        "Content-Type"  = "application/json"

$Result = Invoke-RestMethod @Params -UseBasicParsing
Using the Access Token to Send the REST API Call

Note: Az CLI has an option to list the hybrid connection attached to the Function App.

az functionapp hybrid-connection list --name <Function App Name> --resource-group <Resource Group Name>

So, now we can confirm that the Function App has hybrid connection configured.

Next, we extracted the source code of the function present in the Function App using our internal tool (“VAJRA“) written by Raunak Parmar.

Source Code Extracted

There is an alternative way to read the source code by accessing the management console (SCM Portal) of the Function App. SCM Portal leverages the KUDU engine that also provides us access to PowerShell and the Command Prompt console via web interface. To gain access to the SCM Portal, we need Contributor / Owner RBAC access, or if a custom role is assigned, then we need “microsoft.web/sites/publish/action” privileges to access the SCM Portal.

Additionally, we can access the SCM Portal using the basic auth credentials that we can extract from the published profile using Az PowerShell.

Basic Auth Credentials Extracted

Note: By default, basic auth and FTP access is enabled for all of the app services and Function Apps.

Now, as we have the credentials for the SCM Portal basic auth, we can leverage the below endpoint for authentication.

Using the Credentials

Once we authenticate, we can click on the “Debug console” and then click on the “PowerShell” option.

Selecting the PowerShell Option

Now we can navigate to “site –> wwwroot –> AzureRelayTesting” and click on the edit icon of the “run.ps1” file.

Note: The Function Name “AzureRelayTesting” may differ.

Clicking ‘Edit’ for run.ps1

Once we click on the edit icon, we will be able to view the source code of the function in the Function App.

Source Code

In the function code, we can see that the username, password, and ComputerName are stored in the environment variables that can be retrieved from the app settings. So we’ll use the SCM Portal to view all the app settings configured in the Function App by clicking on the “Environment” option in the menu.

Selecting the Environment Option

In the above screenshot, we can see that the password is stored in the Key Vault secret object. So ideally the managed identity of the Function App will have privileges to read the secrets from the Key Vault.

Managed Identity

Managed identity can be used to gain access to other resources that support Microsoft Entra ID authentication without the need for explicit credentials or secrets. It is a special type of service principal that can be leveraged as identity while granting the RBAC roles in Microsoft Entra ID. More information about managed identity can be found in Microsoft Docs.

To validate if the managed identity has access to the Key Vault, we can enumerate the Key Vault and list all the assigned permissions.

Key Vault Assigned Permissions

In the above screenshot, we see that the Key Vault is leveraging the access policy for access management and there are two Object IDs; those are configured in the access policy. So, let’s first try to find the Object ID of the managed identity assigned on the Function App and check if the managed identity has any privileges.

Managed Identity Object ID

In the above screenshot, we can see the managed identity Object ID and confirm that it has “get” and “list” privileges.

So now we can leverage the PowerShell console from the SCM Portal of the Function App directly to execute commands on the on-premise machine via PSRemoting.

$password = ConvertTo-SecureString $env:onpremadminpassword -AsPlainText -Force
$credential = [System.Management.Automation.PSCredential]::new($env:onpremadminusername, $password)
Invoke-Command -ComputerName $env:hybridconnectionhostname -Port 5986 -UseSSL -ScriptBlock {hostname} -SessionOption (New-PSSessionOption -SkipCACheck) -ErrorAction Stop -Credential $credential
Command Execution on the Machine

We also found that the user had local admin privileges on multiple machines in the target environment. Therefore, we leveraged the same hybrid connection to move laterally to other on-premise machines and found that the user had local admin privileges on a machine that had “AADConnect” agent installed for hybrid identity. The access to the AADConnect machine gave us a path to gain global admin privileges in the target tenant.

$password = ConvertTo-SecureString $env:onpremadminpassword -AsPlainText -Force
$credential = [System.Management.Automation.PSCredential]::new($env:onpremadminusername, $password)
Invoke-Command -ComputerName $env:hybridconnectionhostname -Port 5986 -UseSSL -ScriptBlock {Invoke-Command -ComputerName AADConnect -Credential $Using:credential -ScriptBlock {hostname} } -SessionOption (New-PSSessionOption -SkipCACheck) -ErrorAction Stop -Credential $credential
Discovered a Machine with AADConnect Agent Installed

So there you have it. Through some clever maneuvering, we successfully leveraged PSRemoting for lateral movement from the cloud to on-premises. We hope this post is helpful for learning about and exploring similar attack paths while doing cloud penetration tests.

Feel free to provide us with feedback on Twitter @chiragsavla94 & @trouble1_raunak

And special thanks to all our friends who helped, supported, and motivated us to write this blog post.

Reference Links


Posted by:
Chirag Savla
Senior Cloud Security Engineer

Raunak Parmar
Senior Cloud Security Engineer