Oct 31 2024 |
Maestro: Abusing Intune for Lateral Movement Over C2
If I have a command and control (C2) agent on an Intune admin’s workstation, I should just be able to use their privileges to execute a script or application on an Intune-enrolled device, right?
Not so fast.
I Wanna Go Fast!
- Take me to the GitHub repo!
- Take me to the attack path walkthrough!
- Take me to the defensive guidance!
The Problem
We often find ourselves in the context of a cloud administrator when following attack paths to objectives that require privileged access to Azure-hosted services. We want to use their Entra ID account’s privileges to execute actions in Azure, for example running arbitrary code on remote Intune devices (a.k.a. the “Death from Above” attack path detailed by Andy Robbins), but we have some hurdles to overcome to accomplish this from a C2 agent:
- We don’t have the user’s cleartext password
- Conditional access policies (CAPs) require multi-factor authentication (MFA) for access to the Intune Portal and/or a compliant, hybrid-joined device on a trusted network
- We need to maintain stealth
- We don’t have the knowledge, time, or patience to manipulate tokens and navigate the Azure portal or multiple tools
Let’s look at these problems one at a time and discuss the options available to us.
No Cleartext Credentials / MFA Required
No password? No problem. We already asked the admin nicely for their creds and they didn’t bite, and their password hygiene on the host is solid, but if the device has an identity in Entra ID, we can dump primary refresh token (PRT) cookies from the machine with tools like Lee Chagolla-Christensen’s RequestAADRefreshToken, Dirk-jan Mollema’s ROADToken, Evan McBroom’s LSA Whisperer, Daniel Heinsen’s, SharpGetEntraToken, or aad_prt_bof by wotwot563. These PRT cookies will even have an MFA claim if Windows Hello for Business was used for logon, allowing us to comply with MFA requirements enforced by CAPs or the new default security policy for Azure sign-in.
Stealth
We want to only execute tools that are code-signed and legitimately used in the environment, otherwise keep tool execution within our current process or proxied into the environment from a machine we control that isn’t subject to the organization’s security stack.
We could use our shiny new PRT cookie to interact with Azure using a web browser proxied through the administrator’s workstation, but:
- fumbling through the portal requires prior experience or time to figure out, and features are added, removed, or moved all the time
- information made available by the Azure APIs we’re interested in is not always displayed to the user or is difficult to find
- we have to take frequent screenshots and manual activity logs for deconfliction and reporting instead of letting our C2 framework handle all that, gross
- we may still hit CAPs preventing access without a compliant and/or hybrid-joined device
We could use our C2 agent to run command line tools that are likely to be already installed on cloud administrator workstations (e.g., PowerShell’s Invoke-RestMethod, Microsoft.Graph, AzureAD, or Intune modules, curl.exe, etc.) and interact with Azure APIs, but they don’t directly support BYO PRT cookies, they require multiple steps to obtain refresh and access tokens after dumping a PRT cookie (let alone execute actions with those tokens), and they may generate suspicious parent/child process relationships and command line arguments.
We could dump refresh or access tokens from the memory of applications the cloud admin has used to authenticate to Entra ID (e.g., their browser, the ConfigMgr console in co-management setups, etc.) with tools like the office_tokens BOF from TrustedSec, but we need some luck to obtain creds with the correct client ID, scope, and resource for the actions we want to take or that can be swapped for creds that meet these requirements.
Device Compliance / Hybrid-joined Device
An appropriately scoped refresh or access token would enable us to proxy in excellent open-source tools like ROADTools, AADInternals, BARK, AzureHound, TokenTactics/TokenTacticsV2, or GraphRunner, but we still may be blocked by CAPs requiring device compliance or a hybrid-joined device when exchanging refresh tokens for access tokens or by Continuous Access Evaluation when trying to use stolen access tokens. Further, we need a solid understanding of token manipulation, which at least for me was confusing and not easy to learn without a lot of help, trial and error, and training from Dirk-jan Mollema at Outsider Security.
While it’s possible to fake device compliance with AADInternals thanks to Dr. Nestori Syynimaa (@DrAzureAD) and just a few days ago, Dirk-jan Mollema posted a screenshot of a fake device he created that satisfied ten device compliance requirements, these techniques are not guaranteed to work in all organizations. Device compliance may also be determined and reported to Intune by Microsoft Configuration Manager (formerly SCCM) if the device is co-managed, complicating this attack vector further.
It’s also possible to satisfy conditional access policies requiring a hybrid-joined/registered device by joining a rogue/fake device to Entra ID or by overwriting an existing one, but these techniques require credentials to execute and don’t necessarily allow us to enroll the device in Intune to achieve device compliance.
In any case, CAPs may prevent us from proxying in the tools needed to pull off these attacks or from using them off network, and executing PowerShell directly on the admin’s workstation is risky. I’d rather just use the Intune admin’s compliant hybrid-joined device to reach my objectives.
Using the Azure Portal and Other Tools
Let’s say the problems above are all figured out or we luck out and stumble upon an access token with exactly the attributes we need and there are no CAPs preventing us from using it. We still need to figure out how to use the token to make the appropriate calls to the Microsoft Graph API to execute scripts, applications, and queries on Intune-enrolled devices, which is a complicated, multi-step process.
The Solution
These problems kept resurfacing and none of the available options were ideal for my use case, so I decided to write Maestro to automate them away.
Maestro, an open-source tool sponsored by SpecterOps, was first released at DEF CON 2024 Demo Labs. You can find the code here. If compiling from source, make sure you use the Release build (or the copy on GitHub, if you trust me 😉) so that all of Maestro’s dependencies are merged into the standalone executable with dnMerge by Ceri Coburn (@_ethicalchaos_).
Maestro is essentially a wrapper for local PRT cookie requests and calls to the Microsoft Graph API with a lot of quality-of-life features added for red teamers. It allows you to execute apps, scripts, and device query on Intune-enrolled devices from a C2 agent on an admin’s workstation, no password or proxy required. Data can be optionally stored in a LiteDB database that Maestro will check for valid tokens when subsequent commands are executed.
Maestro takes care of acquiring all the necessary tokens and making the right HTTP requests to execute the action you’re attempting to take in the context of an Intune administrator. Specifically, Maestro mimics the way Edge obtains a nonce from Azure and a signed PRT cookie from the local TPM, then signs in to Intune with the authorization code OAuth flow and obtains an appropriately scoped access token for the requested resource from the mysterious DelegationToken endpoint.
Executing the “Death from Above” Attack Path
Let’s walk through how to execute scripts, applications, and device query on Intune devices with Maestro using the Mythic C2 framework by Cody Thomas (@its_a_feature_) and the SpecterOps Apollo Mythic C2 agent for inline assembly execution.
If something goes wrong, keep in mind that you can run any Maestro command with the `-h` flag to display its full usage.
Let’s say our objective is to gain access to a code repository that only Chris Thompson has access to, and we suspect that there is credential material in their home directory, so we’d like to move laterally to their workstation.
Recon / Locating Users
First, we’ll get a list of Intune devices where cthompson is the primary user, including what users were logged on the last time the device checked in. When the -d option is set, Maestro searches the specified database file for a valid access token with the required scope, then refresh tokens, and finally PRTs before getting a nonce from Azure and fetching a PRT cookie signed by the local system’s TPM to authenticate subsequent requests.
.\Maestro.exe -d <name>.db get intune devices -f “userPrincipalName eq ‘<user>@<tenant>.onmicrosoft.com'” -p usersLoggedOn
Next, Maestro uses the PRT cookie to request a code and id_token from Azure, uses them to obtain a portalAuthorization blob containing a refresh token from the authorize endpoint, and uses the refresh token to obtain an access token for Intune device management from the DelegationToken endpoint.
Then Maestro uses the access token to obtain the list of Intune devices where cthompson is the primary user, including their device IDs and the IDs of users logged on.
If we execute the same command again, Maestro will reuse the access token rather than going through the entire authentication flow again.
Next, we’ll resolve the user ID bfb6a9c2-f3c8–4b9c-9d09–2924d38895f7 from the output of the previous command to a principal name, noting that it was cthompson with domain SID S-1–12–1–3216419266–1268577224–606669213–4153772243 who was logged into the Intune device with ID e537180b-6d04–427e-bf93-dbde818400eb the last time it checked in.
.\Maestro.exe -d Maestro.db get entra users -i <id> –reauth
Device Query
We’ll make sure that the user is still logged in right now using device query, which, similar to SCCM’s CMPivot feature, allows real-time read access to Intune enrolled devices. To do that, we’ll query the Windows Registry HKEY_USERS hive, which indicates that our target user’s domain SID has a session on the device right now.
.\Maestro.exe -d <name>.db exec intune device-query -i <device_id> -q ‘WindowsRegistry(“””HKU*”””‘)
Script Execution
On another device, we’ll use PowerShell to encode a script we’d like to execute on the Intune device. We’ll just write a new file to the C drive for the purpose of this demonstration, but the world is your oyster here.
# Execute a script on the system $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"; $script = "New-Item -Path 'C:' -Name ""Maestro_Executed_$timestamp.txt"" -ItemType ""File"""; $bytes = [System.Text.Encoding]::UTF8.GetBytes($script); $encoded = [Convert]::ToBase64String($bytes); Write-Output $encoded
Finally, we’ll execute the script on the target device then periodically check whether the script has executed and whether the first line of output is available, which is all that is made available by MS Graph. This can take a REALLY long time, especially after running multiple Intune scripts in a short time, but you can use the -t 0 option if you want to wait as long as it takes to recover the script’s output.
.\Maestro.exe -d <name>.db exec intune script -i <device_id> -s <base64_encoded_script> -n <script_name> -t 0
If you don’t want to hang your C2 agent or if your access token expires before the script output is available, you can request output manually as well.
.\Maestro.exe -d <name>.db get intune script-output -i <script_id> — device <device_id>
Unfortunately, at the time of this writing, there is no FileContent device query action, so we can’t read the full contents of arbitrary files from remote devices with device query yet. You’ll have to get creative with script/application execution to accomplish that.
Application Execution
To execute an arbitrary application on an Intune device, simply point Maestro to the UNC path of the executable and either specify an Intune device ID, Entra device ID, or the ID of an existing Entra group containing the ID of the machine to execute the app on. If a device ID is specified, Maestro will automatically locate the device in both Intune and Entra and add it to a new Entra group before executing the application.
.\Maestro.exe -d <name>.db exec intune app -p <unc_path> -i <device_id>
Maestro will attempt to force the device to sync with Intune to fetch applications pending installation via its persistent notification channel, but this fails pretty often because Microsoft severely throttles the number of sync actions a device can make in a short amount of time. In this case, you can use the exec intune sync command to try again or wait until the device’s next naturally occurring check-in (every eight hours or after rebooting by default).
Maestro does not clean up the new app and Entra group automatically because of this uncertain execution window, but the commands needed to execute cleanup manually are displayed to the user.
Eventually (the only unit of time Intune supports 🥲), you should see the results of your application execution.
Using the Database
Right now, the best way to review the contents of the database file is by opening it in LiteDB, where you can review previously queried objects and credentials obtained from Azure.
What’s next for Maestro?
- More flexible token manipulation and storage
- Additional methods of execution
- Credential gathering via Intune (Cloud LAPS)
- Azure Arc support
- Command line options for items in the database
- More enumeration techniques with Entra and Resource Graph
Defensive Guidance
Brett Hawkins at IBM X-Force Red recently wrote an excellent, very detailed blog post about detecting Intune lateral movement techniques and provided Sentinel-ready KQL queries that can be used to alert upon suspicious Intune script and application execution. There are lots of events of interest generated from this attack path that the KQL queries can be modified to support.
When creating detections, keep in mind that the attacker can control the information that Maestro and similar tools send to Microsoft Graph in HTTP requests (e.g., User Agent, time between requests, etc.). If possible, baseline your Intune admins’ legitimate activities and alert upon suspicious usage of these features. For example, if Intune apps are deleted very rarely in your organization or are typically created several days before they are assigned, those events might be indicators of compromise.
Have questions or want to collaborate on new functionality or research?
Hit me up on Twitter (@_Mayyhem) or in the BloodHound Slack!
Maestro was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.