Add WireServer & GoalState

This commit is contained in:
Jimmy
2026-05-05 14:25:14 +02:00
parent a80e28427b
commit b0cd651c8d
@@ -837,6 +837,317 @@ 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/?comp=goalstate
```
### Access Restrictions
Although the endpoint is reachable from the VM network, **it is not equally accessible from all contexts**.
**Accessible from**:
- Azure **VM Agent**
- Azure **Run Command**
- **VM Extensions**
**Not reliably accessible from**:
- Interactive SSH sessions (e.g., `azureuser`)
- Unprivileged processes inside the VM
This is because:
- The WireServer is designed for **platform-agent communication**
- Requests may require **specific headers, timing, or context**
- Some responses are only available to the **VM Agent execution environment**
---
### Run Command vs SSH Context
Azure provides multiple ways to execute commands inside a VM, but **they do not run in the same context**.
---
#### Run Command
Run Command is an Azure feature that executes scripts via the **VM Agent**.
- Uses: `Microsoft.Compute/virtualMachines/runCommand/action`
- Runs with **agent-level privileges**
- Has access to:
- WireServer
- GoalState
- ExtensionsConfig
Example:
```bash
az vm run-command invoke \
--resource-group <rsc-group> \
--name <vm-name> \
--command-id RunShellScript \
--scripts @script.sh
```
#### SSH Session
When connecting via SSH:
- Runs as a **regular OS user**
- Uses standard network stack
- Does **NOT have agent-level access**
As a result:
- Requests to `168.63.129.16` may fail or return incomplete data
- GoalState may not be accessible
**Script Examples:**
{{#tabs }}
{{#tab name="Linux" }}
```bash
#!/usr/bin/env bash
set -euo pipefail
ws="http://168.63.129.16"
echo "[*] Getting Goal State..."
goal_urls=(
"$ws/?comp=goalstate"
"$ws/machine?comp=goalstate"
"$ws/machine/?comp=goalstate"
)
goal_xml=""
for url in "${goal_urls[@]}"; do
if goal_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$url" 2>/dev/null)"; then
echo "[+] GoalState OK via $url"
break
fi
done
if [[ -z "$goal_xml" ]]; then
echo "[-] No GoalState endpoint responded"
exit 1
fi
ext_url="$(
GOAL_XML="$goal_xml" python3 - <<'PY'
import os
import xml.etree.ElementTree as ET
xml = os.environ["GOAL_XML"].strip()
root = ET.fromstring(xml)
def lname(tag):
return tag.rsplit("}", 1)[-1]
for el in root.iter():
if lname(el.tag) == "ExtensionsConfig" and (el.text or "").strip():
print(el.text.strip())
break
PY
)"
if [[ -z "$ext_url" ]]; then
echo "[-] No ExtensionsConfig URL found in GoalState"
echo "[*] Identity-like nodes seen in GoalState:"
GOAL_XML="$goal_xml" python3 - <<'PY'
import os
import xml.etree.ElementTree as ET
xml = os.environ["GOAL_XML"].strip()
root = ET.fromstring(xml)
def lname(tag):
return tag.rsplit("}", 1)[-1]
found = False
for el in root.iter():
name = lname(el.tag)
if "Identity" in name:
found = True
text = (el.text or "").strip()
print(f"<{name}>{text}</{name}>")
if not found:
print(" (none)")
PY
exit 0
fi
echo "[*] Getting ExtensionsConfig..."
ext_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$ext_url")"
EXT_XML="$ext_xml" python3 - <<'PY'
import os
import xml.etree.ElementTree as ET
xml = os.environ["EXT_XML"].strip()
root = ET.fromstring(xml)
def lname(tag):
return tag.rsplit("}", 1)[-1]
ids = [el for el in root.iter() if lname(el.tag) == "UserAssignedIdentity"]
if not ids:
print("[-] No UserAssignedIdentity nodes found")
print("[*] Identity-like nodes present in ExtensionsConfig:")
shown = False
for el in root.iter():
name = lname(el.tag)
if "Identity" in name:
shown = True
text = (el.text or "").strip()
attrs = " ".join(f'{k}="{v}"' for k, v in el.attrib.items())
if attrs:
print(f" <{name} {attrs}>{text}</{name}>")
else:
print(f" <{name}>{text}</{name}>")
if not shown:
print(" (none)")
raise SystemExit(0)
for idnode in ids:
client_id = ""
object_id = ""
resource_id = ""
for child in idnode.iter():
name = lname(child.tag)
text = (child.text or "").strip()
if name == "IdentityClientId":
client_id = text
elif name == "IdentityObjectId":
object_id = text
elif name == "IdentityResourceId":
resource_id = text
print()
print("[+] Managed Identity:")
print(f" ClientId : {client_id}")
print(f" ObjectId : {object_id}")
print(f" ResourceId : {resource_id}")
PY
```
{{#endtab }}
{{#tab name="Windows" }}
```bash
$ws = "http://168.63.129.16"
$h = @{
"x-ms-version" = "2012-11-30"
}
Write-Host "[*] Getting Goal State..." -ForegroundColor Cyan
$goalUrls = @(
"$ws/?comp=goalstate",
"$ws/machine?comp=goalstate",
"$ws/machine/?comp=goalstate"
)
$gs = $null
foreach ($url in $goalUrls) {
try {
$gs = Invoke-WebRequest -Uri $url -Headers $h -UseBasicParsing -ErrorAction Stop
Write-Host "[+] GoalState OK via $url" -ForegroundColor Green
break
} catch {}
}
if (-not $gs) {
Write-Host "[-] No GoalState endpoint responded" -ForegroundColor Red
return
}
[xml]$xml = $gs.Content
$cfg = $xml.GoalState.Container.RoleInstanceList.RoleInstance.Configuration
$extUrl = $cfg.ExtensionsConfig
Write-Host "[*] Getting ExtensionsConfig..." -ForegroundColor Cyan
try {
$ext = Invoke-WebRequest -Uri $extUrl -Headers $h -UseBasicParsing -ErrorAction Stop
[xml]$extXml = $ext.Content
} catch {
Write-Host "[-] Error getting ExtensionsConfig" -ForegroundColor Red
return
}
# Extract Managed Identity info
$ids = $extXml.SelectNodes("//UserAssignedIdentity")
if (!$ids) {
Write-Host "[-] No User Assigned Identities found" -ForegroundColor Red
return
}
foreach ($id in $ids) {
$clientId = $id.IdentityClientId
$objectId = $id.IdentityObjectId
$resourceId = $id.IdentityResourceId
Write-Host "`n[+] Managed Identity:" -ForegroundColor Green
Write-Host " ClientId : $clientId"
Write-Host " ObjectId : $objectId"
Write-Host " ResourceId : $resourceId"
}
```
{{#endtab }}
{{#endtabs }}
## Privilege Escalation
{{#ref}}
@@ -866,6 +1177,9 @@ 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/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/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)
{{#include ../../../../banners/hacktricks-training.md}}