mirror of
https://github.com/HackTricks-wiki/hacktricks-cloud.git
synced 2026-06-12 11:01:38 -07:00
Merge pull request #286 from JaimePolop/master
Add WireServer & GoalState
This commit is contained in:
@@ -837,6 +837,227 @@ Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
## Azure WireServer & GoalState
|
||||
|
||||
Azure VMs expose **internal platform endpoints** that are used for configuration, metadata retrieval and identity management. Understanding the difference between them is critical for **enumeration, privilege escalation and post-exploitation**.
|
||||
|
||||
### Wire Server (Azure Fabric Endpoint)
|
||||
|
||||
The **Azure WireServer** is an internal Azure IP (`168.63.129.16`) used by the platform to communicate with the VM.
|
||||
|
||||
It is responsible for:
|
||||
|
||||
- Communication with the **VM Agent**
|
||||
- Delivering:
|
||||
- **GoalState**
|
||||
- **ExtensionsConfig**
|
||||
- Internal VM configuration (including identities)
|
||||
- DHCP & DNS services
|
||||
- Health monitoring
|
||||
|
||||
### GoalState & ExtensionsConfig
|
||||
|
||||
The **GoalState** represents the **desired configuration of the VM** as defined by Azure. It may include:
|
||||
|
||||
- Extensions configuration
|
||||
- Managed identities
|
||||
- Provisioning state
|
||||
- Agent instructions
|
||||
|
||||
The **ExtensionsConfig** contains detailed configuration of VM extensions and may include:
|
||||
|
||||
- **User Assigned Managed Identities**
|
||||
- Extension settings
|
||||
- Secrets (depending on extension)
|
||||
|
||||
These endpoints are typically accessed via:
|
||||
|
||||
```bash
|
||||
curl -H "x-ms-version: 2012-11-30" http://168.63.129.16/machine?comp=goalstate
|
||||
```
|
||||
|
||||
### Access considerations
|
||||
|
||||
The WireServer IP is generally reachable from inside the VM through the guest network stack. It is not restricted only to the Azure VM Agent, Run Command, or VM extensions. Microsoft even documents agentless Linux provisioning examples where ordinary in-guest scripts query GoalState directly from `168.63.129.16`.
|
||||
|
||||
However, not every process will necessarily get the same practical result:
|
||||
|
||||
- Some endpoints require Azure-specific headers, such as `x-ms-version: 2012-11-30` for GoalState.
|
||||
- Local guest controls can block or alter access, including host firewall rules, proxies, routes, network namespaces, containers, or endpoint protection.
|
||||
- VM extensions and Run Command commonly execute as `root`/`SYSTEM` through the VM Agent, so they may bypass local OS restrictions that affect an interactive user.
|
||||
- Some data is agent/extension-specific and may depend on the VM's provisioning state, installed agent, configured extensions, or managed identity configuration.
|
||||
|
||||
Therefore, if a request works from Run Command but fails from SSH, the usual explanation is a difference in OS user, environment, routing, proxy, firewall, or namespace, not a general Azure rule that only agent execution contexts can reach `168.63.129.16`.
|
||||
|
||||
In lab testing this distinction was visible: Linux/Windows VM Agent execution through Run Command or Custom Script extensions could reach GoalState on `168.63.129.16`, while a normal SSH session on another Linux VM could still reach IMDS but timed out when querying GoalState. Treat WireServer/GoalState as useful but environment-dependent; do not rely on it as the canonical way to enumerate managed identities.
|
||||
|
||||
### Managed Identity Access From Inside the VM
|
||||
|
||||
The reliable way to use a VM's managed identities is the **IMDS managed identity endpoint** at `169.254.169.254`, not the WireServer `ExtensionsConfig` XML. Scripts that only search `ExtensionsConfig` for `UserAssignedIdentity` nodes are not reliable because:
|
||||
|
||||
- The VM's managed identity assignment is not guaranteed to be represented as `UserAssignedIdentity` nodes in extension XML.
|
||||
- They miss **system-assigned managed identities**.
|
||||
- They only find user-assigned identities if the current GoalState/extension data happens to expose the expected XML shape.
|
||||
|
||||
Microsoft's documented security model is that **all code running on the VM can request tokens for the managed identities available on that VM**. This was confirmed from:
|
||||
|
||||
- Linux SSH as a regular VM user.
|
||||
- Linux Run Command through the VM Agent.
|
||||
- Linux Custom Script extension through the VM Agent.
|
||||
- Windows Custom Script extension as `NT AUTHORITY\SYSTEM`.
|
||||
|
||||
In all of those contexts, IMDS could mint tokens for Management, Microsoft Graph/Entra ID, Key Vault, and Storage when the requested identity was available to the VM.
|
||||
|
||||
There are two different problems that are easy to mix up:
|
||||
|
||||
- **Getting a token for a known identity:** If the identity is assigned to the VM, IMDS can issue tokens for different audiences such as `https://management.azure.com/`, `https://graph.microsoft.com/`, `https://vault.azure.net`, and `https://storage.azure.com/`. If several user-assigned identities exist, request a specific one with `client_id`, `object_id`, or `msi_res_id`.
|
||||
- **Discovering every attached identity from inside the VM:** IMDS does not provide a simple "list all identities" endpoint. A practical method is to get a default Management token, read the VM resource through ARM, and inspect the `identity` property. This only works if that managed identity has permissions such as `Microsoft.Compute/virtualMachines/read` on the VM. If ARM returns `403`, the token can still be valid and useful, but it cannot enumerate the VM's full identity list.
|
||||
|
||||
If ARM discovery fails, you can still try WireServer/HostGAPlugin sources such as GoalState and `http://168.63.129.16:32526/vmSettings` to look for identity-looking fields (`clientId`, `IdentityClientId`, `msi_res_id`, user-assigned identity resource IDs) and then ask IMDS for tokens with those selectors. This is a fallback, not a guarantee: those endpoints are context-dependent and may expose no managed identity selectors at all.
|
||||
|
||||
The following examples first request a token. Then they try to read the VM resource from Azure Resource Manager and print its `identity` property. The second step only works if the managed identity has permissions such as `Microsoft.Compute/virtualMachines/read` on the VM.
|
||||
|
||||
{{#tabs }}
|
||||
{{#tab name="Linux" }}
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
imds="http://169.254.169.254/metadata"
|
||||
api_version="2021-02-01"
|
||||
resource="${1:-https://management.azure.com/}"
|
||||
|
||||
# Optional. Examples:
|
||||
# export MSI_SELECTOR='client_id=<client-id>'
|
||||
# export MSI_SELECTOR='object_id=<principal-id>'
|
||||
# export MSI_SELECTOR='msi_res_id=/subscriptions/.../userAssignedIdentities/name'
|
||||
selector="${MSI_SELECTOR:-}"
|
||||
|
||||
urlencode() {
|
||||
python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1"
|
||||
}
|
||||
|
||||
token_url="$imds/identity/oauth2/token?api-version=$api_version&resource=$(urlencode "$resource")"
|
||||
if [[ -n "$selector" ]]; then
|
||||
token_url="$token_url&$selector"
|
||||
fi
|
||||
|
||||
echo "[*] Requesting managed identity token for: $resource"
|
||||
token_json="$(curl -fsS --noproxy "*" -H "Metadata:true" "$token_url")"
|
||||
|
||||
access_token="$(
|
||||
TOKEN_JSON="$token_json" python3 - <<'PY'
|
||||
import json, os
|
||||
print(json.loads(os.environ["TOKEN_JSON"])["access_token"])
|
||||
PY
|
||||
)"
|
||||
|
||||
TOKEN="$access_token" python3 - <<'PY'
|
||||
import base64, json, os
|
||||
|
||||
token = os.environ["TOKEN"]
|
||||
payload = token.split(".")[1]
|
||||
payload += "=" * (-len(payload) % 4)
|
||||
claims = json.loads(base64.urlsafe_b64decode(payload))
|
||||
|
||||
print("[+] Token acquired")
|
||||
for key in ("tid", "appid", "oid", "xms_mirid"):
|
||||
if key in claims:
|
||||
print(f" {key}: {claims[key]}")
|
||||
PY
|
||||
|
||||
echo "[*] Trying to read the VM identity property through ARM..."
|
||||
compute_json="$(curl -fsS --noproxy "*" -H "Metadata:true" "$imds/instance/compute?api-version=$api_version")"
|
||||
vm_id="$(
|
||||
COMPUTE_JSON="$compute_json" python3 - <<'PY'
|
||||
import json, os
|
||||
print(json.loads(os.environ["COMPUTE_JSON"])["resourceId"])
|
||||
PY
|
||||
)"
|
||||
|
||||
arm_url="https://management.azure.com${vm_id}?api-version=2024-07-01"
|
||||
if vm_json="$(curl -fsS -H "Authorization: Bearer $access_token" "$arm_url" 2>/dev/null)"; then
|
||||
VM_JSON="$vm_json" python3 - <<'PY'
|
||||
import json, os
|
||||
vm = json.loads(os.environ["VM_JSON"])
|
||||
print(json.dumps(vm.get("identity", {}), indent=2))
|
||||
PY
|
||||
else
|
||||
echo "[-] Could not read the VM resource with this identity. The token may still be valid, but it lacks ARM read permissions on the VM."
|
||||
fi
|
||||
```
|
||||
|
||||
{{#endtab }}
|
||||
|
||||
{{#tab name="Windows" }}
|
||||
|
||||
```powershell
|
||||
$imds = "http://169.254.169.254/metadata"
|
||||
$apiVersion = "2021-02-01"
|
||||
$resource = "https://management.azure.com/"
|
||||
|
||||
# Optional. Examples:
|
||||
# $env:MSI_SELECTOR = "client_id=<client-id>"
|
||||
# $env:MSI_SELECTOR = "object_id=<principal-id>"
|
||||
# $env:MSI_SELECTOR = "msi_res_id=/subscriptions/.../userAssignedIdentities/name"
|
||||
$selector = $env:MSI_SELECTOR
|
||||
|
||||
function Invoke-Imds {
|
||||
param([string]$Uri)
|
||||
|
||||
$params = @{
|
||||
Method = "GET"
|
||||
Uri = $Uri
|
||||
Headers = @{ Metadata = "true" }
|
||||
UseBasicParsing = $true
|
||||
}
|
||||
if ((Get-Command Invoke-RestMethod).Parameters.ContainsKey("NoProxy")) {
|
||||
$params.NoProxy = $true
|
||||
}
|
||||
Invoke-RestMethod @params
|
||||
}
|
||||
|
||||
function Decode-JwtPayload {
|
||||
param([string]$Token)
|
||||
|
||||
$payload = $Token.Split(".")[1].Replace("-", "+").Replace("_", "/")
|
||||
switch ($payload.Length % 4) {
|
||||
2 { $payload += "==" }
|
||||
3 { $payload += "=" }
|
||||
}
|
||||
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payload)) | ConvertFrom-Json
|
||||
}
|
||||
|
||||
$encodedResource = [uri]::EscapeDataString($resource)
|
||||
$tokenUri = "$imds/identity/oauth2/token?api-version=$apiVersion&resource=$encodedResource"
|
||||
if ($selector) {
|
||||
$tokenUri = "$tokenUri&$selector"
|
||||
}
|
||||
|
||||
Write-Host "[*] Requesting managed identity token for: $resource"
|
||||
$tokenResponse = Invoke-Imds -Uri $tokenUri
|
||||
$claims = Decode-JwtPayload -Token $tokenResponse.access_token
|
||||
|
||||
Write-Host "[+] Token acquired"
|
||||
$claims | Select-Object tid, appid, oid, xms_mirid | Format-List
|
||||
|
||||
Write-Host "[*] Trying to read the VM identity property through ARM..."
|
||||
$compute = Invoke-Imds -Uri "$imds/instance/compute?api-version=$apiVersion"
|
||||
$armUri = "https://management.azure.com$($compute.resourceId)?api-version=2024-07-01"
|
||||
|
||||
try {
|
||||
$vm = Invoke-RestMethod -Method GET -Uri $armUri -Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" } -UseBasicParsing
|
||||
$vm.identity | ConvertTo-Json -Depth 20
|
||||
} catch {
|
||||
Write-Host "[-] Could not read the VM resource with this identity. The token may still be valid, but it lacks ARM read permissions on the VM."
|
||||
}
|
||||
```
|
||||
|
||||
{{#endtab }}
|
||||
{{#endtabs }}
|
||||
|
||||
|
||||
## Privilege Escalation
|
||||
|
||||
{{#ref}}
|
||||
@@ -866,8 +1087,11 @@ Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-machines/overview](https://learn.microsoft.com/en-us/azure/virtual-machines/overview)
|
||||
- [https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/](https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/)
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service](https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service)
|
||||
- [https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16](https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16)
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-machines/linux/no-agent](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/no-agent)
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-machines/run-command](https://learn.microsoft.com/en-us/azure/virtual-machines/run-command)
|
||||
- [https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux](https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux)
|
||||
- [https://www.cybercx.com.au/blog/azure-ssrf-metadata/](https://www.cybercx.com.au/blog/azure-ssrf-metadata/)
|
||||
|
||||
{{#include ../../../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user