IMDS Hardening.
Background
The Azure Instance Metadata Service (IMDS) is a RESTFUL API providing information about virtual machine instances. Essentially, if you have access to a virtual machine instance that’s hosted on Azure, the well-known non-routable IP address of 169.254.169.254 can be queried using a command such as that shown below.
Note: The HTTP header ‘Metadata‘ will need to be included with all requests.
Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/instance?api-version=2021-02-01' -Headers @{"Metadata"="true"}
Apart from general enumeration, the IMDS could be of interest to a malicious insider if for example, managed identities are used.
In this instance it may be possible to gather an access token for the underlying account and use this elsewhere within the environment, an example follows.
Invoke-WebRequest -Uri 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -Headers @{Metadata="true"}
The outcome of an attack very much depends on what the stolen account has access to. However, that is a blog for another day!
Defence in Depth
There seems to be very little official documentation stating which processes/services should have access to this API and, it seems logical to want to restrict access to this service to only those that should legitimately require access!
There are, however, some relatively easy defence in depth modifications that can be implemented on endpoints to ensure unwanted processes don’t have access to this API.
During our testing we noted that only the WindowsAzureGuestAgent.exe service required access to the IMDS API 169.254.169.254, shown in output of Procmon below.
Incidentally there is another IP address that we won’t discuss here but we need to be aware of, 168.63.129.16, further details can be found here.
During testing we identified that svchost.exe, WindowsAzureGuestAgent.exe and WaAPPAgent.exe all communicated with this IP address.
For completeness we’re also going to include this address in the hardening suggestion, but this can be tweaked/removed if preferred.
Windows Firewall
So, how can we limit which programs can access this API?
The idea is to create a Windows Firewall deny program/application based rule. An individual rule will be created for every program and script that should never access the aforementioned metadata IP addresses.
A good place to start; take a look at LOLBAS. This is a list of known programs that ship with Windows or are otherwise commonly installed, many of which can be used for alternate and potentially malicious purposes.
Some of these are just plain dangerous to have on an end user system (a C# compiler, for example) and should quite obviously be restricted to development and administrative devices!
Putting it all Together
The following script below will firstly query the LOLBAS API for a list of documented programs and scripts. The script will then iterate through each entry in the array and a local check will be performed to see if the program exists on the endpoint. Assuming a match is found, an individual Windows Firewall rule will be created to block the identified program from accessing the IMDS API.
Note: At the time of writing the services that legitimately queried the IMDS API weren’t included within the LOLBAS project, however things change and it’s always best to check that these are excluded from the blocking rule to avoid any downtime or issues with the host!
$response = Invoke-WebRequest -Uri 'https://lolbas-project.github.io/api/lolbas.json' -UseBasicParsing
$binaries = $response | ConvertFrom-Json
$binaries.Name
Before we continue, we’ll also need to manually add both PowerShell.exe and PowerShell_ISE.exe (as well as any other custom/bespoke software you may wish to include) to the $binaries array as these aren’t included within the LOLBAS project.
$binaries += [pscustomobject]@{Name="PowerShell.exe"}
$binaries += [pscustomobject]@{Name="PowerShell_ISE.exe"}
The full script…
Note: An administrative PowerShell terminal will be required for the following command to succeed.
$response = Invoke-WebRequest -Uri 'https://lolbas-project.github.io/api/lolbas.json' -UseBasicParsing
$binaries = $response | ConvertFrom-Json
$binaries += [pscustomobject]@{Name="PowerShell.exe"}
$binaries += [pscustomobject]@{Name="PowerShell_ISE.exe"}
foreach ($exe in $binaries.Name)
{
Get-ChildItem C:\ -Filter $exe -Recurse | Where-Object {$_.FullName -notlike "*\WinSxS\*"} | ForEach-Object {$ExePath = $_.FullName; New-NetFirewallRule -Program $ExePath -Action Block -Profile Domain, Private, Public –RemoteAddress 169.254.169.254,168.63.129.16 -DisplayName "Azure Agent IMDS Block - $ExePath" -Description "Azure Agent IMDS Block - $ExePath" -Direction Outbound -Enabled True}
}
This will take some time to run and you’ll likely see errors for paths that the account doesn’t have access, as well as output stating that rules have been created, an example follows.
Once complete, if you now check the Windows Firewall interface you’ll see an individual rule has been created for each of the programs that have been identified to be present on the host.
Now if an application like PowerShell is used to access the IMDS, it’s blocked!
As always, we’d highly recommend that further application controls are deployed on endpoints, a good place to start is AppLocker and Microsoft’s recommended block list.