Azure Connected Machine Agent Elevation of Privilege Vulnerability: Scheduled Task

  • Sharan Patil Sharan Patil
  • Published: 20 Jan 2026
  • Type: Local Privilege Escalation
  • Severity: High

Affected Products

Azure Connected Machine Agent < 1.55

Summary

Version 1.48 of Azure Arc introduced a feature which checks for agent updates via a scheduled task. This feature could be exploited to achieve Elevation of Privileges (EoP) due to lack of integrity checks.

CVE

CVE-2025-55316

Introduction

In our previous advisory we spoke about the introduction of a scheduled task. Although this task did not automatically update the agent at the time of reporting, it could still be used for escalating our privileges due to lack of integrity checks. The scheduled task azcmagent triggered between 1-2 AM server time every day. This scheduled task would check if automatic upgrade for the agent was enabled by querying the configuration from the himds service over a named pipe. At the time of reporting, the automatic upgrade feature in the himds service was disabled. As such the script would terminate without updating the agent. Like the ExtensionService vulnerability, the script did not validate if the himds service was a legitimate or a rouge service. The lack of integrity checks was once again susceptible to elevation of privileges.

Description

This advisory describes the third vulnerability discovered due to a feature in version 1.48. The scheduled task introduced in version 1.48 contained the task azcmagent to check if automatic updates are enabled as shown below:

At the time of reporting, the task was enabled but the auto-upgrade feature was disabled and later enabled in version 1.57 in a public preview mode. Investigating further, the scheduled task was configured to execute a PowerShell script with the following command:

C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -File "C:\Program Files\AzureConnectedMachineAgent\azcmagent_check_updates.ps1"

This script was responsible for using the binary azcmagent to query for automatic updates. This action was defined at line 120:

$enabled = azcmagent config get automaticupgrade.enabled

As the feature was disabled, the script wrote to a log file and terminated. All the communications between the azcmagent binary and the himds service happened over a named pipe called himds. Access to the named pipe is restricted to privileged users only. However, using msiexec repair functionality, like CVE-2025-47989, we could restart the service and start our malicious himds named pipe.

Understanding The Execution Flow

Before we impersonated the named pipe, we needed to understand how a legitimate connection works. As the scheduled task was executing a PowerShell script, we could look at the script to view the commands being executed. The following lines were the main commands responsible for the update process. The commands along with their line number were extracted from the PowerShell script:

63:  $proxy = (azcmagent config get proxy.url -j | ConvertFrom-Json).Data
111: $show = azcmagent show --json | ConvertFrom-Json
120: $enabled = azcmagent config get automaticupgrade.enabled
130: $desiredVersion = azcmagent config get automaticupgrade.desiredversion
142: $lastattempt = azcmagent config get automaticupgrade.lastattempt.status
154: $lastAttemptTimestamp = azcmagent config get automaticupgrade.lastattempt.timestamp
168: $downloadlink = azcmagent config get automaticupgrade.downloadlink
182: $onboardingScriptPath = Join-Path -Path $env:ProgramFiles -ChildPath "AzureConnectedMachineAgent" | Join-Path -ChildPath "install_azcmagent.ps1"
191: & $onboardingScriptPath -AltDownload $downloadlink -OutFile $outfilePath

The flow of the PowerShell script was as follows:

  1. The azcmagent checked if a proxy was configured for the agent and used the proxy details for the outbound requests
  2. The azcmagent would then retrieve the metadata of the server
  3. Once the metadata was obtained, the azcmagent would check if the automatic upgrade feature was enabled. The code block is shown below:
try 
    {
        $enabled = azcmagent config get automaticupgrade.enabled
        if ($enabled -ne "true") {
            Send-Log "automaticupgrade.enabled not enabled" "AZCM0162" $show
            exit 0
        }
    } catch {
        Invoke-Failure -ErrorCode "AZCM0163" -Message "unable to get automaticupgrade.enabled flag: $_"
    }

The screenshot below shows the message flow between the client and the server when viewed with IONinja’s PipeMonitor tool:

  1. If automatic upgrade was enabled, the latest version to update to will be fetched by the azcmagent
  2. The azcmagent then checked when the last update attempt was made. If this value was less than three days from the current time, then the script would exit as shown below:
try 
   {
       $lastAttemptTimestamp = azcmagent config get automaticupgrade.lastattempt.timestamp
       $lastAttemptTimestamp =  [int]::Parse($lastAttemptTimestamp)
       
       $timeSinceLastAttemptSec = $timestamp - $lastAttemptTimestamp 
       if($timeSinceLastAttemptSec -lt 60 * 60  * 24 * 3){ # 60 seconds/min * 60 min/hr * 24 hr/day * 3 days 
           Send-Log "Last attempt failed, skipping upgrade" "AZCM0168" $show
           exit 0
       }
    } catch {
            Invoke-Failure -ErrorCode "AZCM0166" -Message "unable to check last attempt timestamp $_"
    }
  1. Once these checks were completed and satisfied with all the conditions, the download link for the MSI file would be requested and stored in the variable as shown in the code block below. Additionally, the script also verified if the download link was accessible by the server.
try 
   {
        $downloadlink = azcmagent config get automaticupgrade.downloadlink

        $proxy = (azcmagent config get proxy.url -j | ConvertFrom-Json).Data
        if ($proxy){
             $response = Invoke-WebRequest -UseBasicParsing -Uri $downloadlink -Method Head -Proxy $proxy
        }
        else{
             $response = Invoke-WebRequest -UseBasicParsing -Uri $downloadlink -Method Head 
        }
    } catch {
        Invoke-Failure -ErrorCode "AZCM0169" -Message "unable to get or reach automaticupgrade.downloadlink: $_"
    }
  1. The execution continued with the script storing the onboarding script path (C:\Program Files\AzureConnectedMachineAgent\install_azcmagent.ps1) in a variable defined at line 182:
$onboardingScriptPath = Join-Path -Path $env:ProgramFiles -ChildPath "AzureConnectedMachineAgent" | Join-Path -ChildPath "install_azcmagent.ps1"
  1. Finally, the C:\Program Files\AzureConnectedMachineAgent\install_azcmagent.ps1 script was triggered at line 191, assuming proxy was not configured:
& $onboardingScriptPath -AltDownload $downloadlink -OutFile $outfilePath
  1. Within the install_azcmagent.ps1 script at line 444, a variable $msiFile is defined as shown below:
$msiFile = Join-Path "$env:Temp" "AzureConnectedMachineAgent.msi"
  1. After the variable definition, the function Download-With-Retries was called at line 454. The Download-With-Retries function downloaded the installer and stored it in the location defined in the variable $msiFile as seen in the command:
Invoke-WebRequest -UseBasicParsing -Proxy $Proxy -Uri $downloadUri -OutFile $msiFile
  1. The downloaded script was executed on the next line, with the command:
& "$env:TEMP\install_windows_azcmagent.ps1";

Unfortunately, the download location is C:\Windows\Temp. The exploitation has been covered in a previous writeup. However, this is not the focus point in this scenario as the MSI file would be downloaded from an arbitrary source specified in the PoC.

Building The Exploit

An attacker who could impersonate the named pipe, i.e. creating a malicious named pipe server, could send the malicious upgrade data to the azcmagent client that is interacting with the himds pipe. During the research, the response value was set to true in the execution of $enabled = azcmagent config get automaticupgrade.enabled.

This resulted in the script continuing to execute with the rest of the script. Similarly, PipeMonitor was used to view the legitimate pipe traffic for other commands and update the response within the named pipe as required.

As the scheduled task was executed as NT AUTHORITY\SYSTEM, the execution of the task failed with the following error:

C:\Program Files\AzureConnectedMachineAgent\azcmagent_check_updates.ps1 : unable to get or reach automaticupgrade.downloadlink: The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again. + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,azcmagent_check_updates.ps1

Debugging the root cause, it was found that the Internet Explorer's first-launch configuration was enabled. While different users may have different deployment methods, the proof-of-concept assumed that Internet Explorer's first-launch configuration was overcome with either system administrators disabling the feature by setting the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\DisableFirstRunCustomize as DWORD 2 or some other component of the system had completed Internet Explorer's first-launch configuration leading to no errors. Although error handling was implemented in the try-catch blocks of the PowerShell scripts, the lack of integrity checks could be exploited to download and execute a malicious msi file.

Proof of Concept

  1. Create a malicious MSI file and host it over a web server. Set up a listener for a reverse shell.
  2. Create the malicious named pipe server binary.
  3. Identify the MSI file belonging to Azure Arc using the msi_search.ps1 script.
  4. Trigger a msiexec repair with the command: msiexec.exe /fa C:\Windows\Installer\name.msi
  5. Wait for the process himds.exe to terminate before starting a rouge named pipe server.
  6. Trigger the scheduled task azcmagent manually or wait for the azcmagent trigger between 01:00 and 02:00.

Expected Result

The installation script should download and execute the legitimate Azure Arc MSI installer.

Observed Result

After successfully completing the update process a malicious installer is executed leading to local privilege escalation.

The Fix

The azcmagent now verifies the owner of the named pipe himds before interacting with the server. The azcmagent stops interacting with the named pipe if the owner is not the himds local service account or the local administrator.

Remediation

Update the agent to the latest version available.

Timeline

Date Action
23 Jul 2025 Initial disclosure
24 Jul 2025 Report under review
2 Sep 2025 Vulnerability confirmed
9 Sep 2025 CVE-2025-55316 published