Skip to content

Commit b0cd651

Browse files
JimmyJimmy
authored andcommitted
Add WireServer & GoalState
1 parent a80e284 commit b0cd651

1 file changed

Lines changed: 314 additions & 0 deletions

File tree

  • src/pentesting-cloud/azure-security/az-services/vms

src/pentesting-cloud/azure-security/az-services/vms/README.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,317 @@ Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
837837
{{#endtab }}
838838
{{#endtabs }}
839839
840+
## Azure WireServer & GoalState
841+
842+
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**.
843+
844+
---
845+
846+
### Wire Server (Azure Fabric Endpoint)
847+
848+
The **Azure WireServer** is an internal Azure IP (`168.63.129.16`) used by the platform to communicate with the VM.
849+
850+
It is responsible for:
851+
852+
- Communication with the **VM Agent**
853+
- Delivering:
854+
- **GoalState**
855+
- **ExtensionsConfig**
856+
- Internal VM configuration (including identities)
857+
- DHCP & DNS services
858+
- Health monitoring
859+
860+
---
861+
862+
### GoalState & ExtensionsConfig
863+
864+
The **GoalState** represents the **desired configuration of the VM** as defined by Azure. It may include:
865+
866+
- Extensions configuration
867+
- Managed identities
868+
- Provisioning state
869+
- Agent instructions
870+
871+
The **ExtensionsConfig** contains detailed configuration of VM extensions and may include:
872+
873+
- **User Assigned Managed Identities**
874+
- Extension settings
875+
- Secrets (depending on extension)
876+
877+
These endpoints are typically accessed via:
878+
879+
```bash
880+
curl -H "x-ms-version: 2012-11-30" http://168.63.129.16/?comp=goalstate
881+
```
882+
883+
### Access Restrictions
884+
885+
Although the endpoint is reachable from the VM network, **it is not equally accessible from all contexts**.
886+
887+
**Accessible from**:
888+
889+
- Azure **VM Agent**
890+
- Azure **Run Command**
891+
- **VM Extensions**
892+
893+
**Not reliably accessible from**:
894+
895+
- Interactive SSH sessions (e.g., `azureuser`)
896+
- Unprivileged processes inside the VM
897+
898+
This is because:
899+
900+
- The WireServer is designed for **platform-agent communication**
901+
- Requests may require **specific headers, timing, or context**
902+
- Some responses are only available to the **VM Agent execution environment**
903+
904+
---
905+
906+
### Run Command vs SSH Context
907+
908+
Azure provides multiple ways to execute commands inside a VM, but **they do not run in the same context**.
909+
910+
---
911+
912+
#### Run Command
913+
914+
Run Command is an Azure feature that executes scripts via the **VM Agent**.
915+
916+
- Uses: `Microsoft.Compute/virtualMachines/runCommand/action`
917+
- Runs with **agent-level privileges**
918+
- Has access to:
919+
- WireServer
920+
- GoalState
921+
- ExtensionsConfig
922+
923+
Example:
924+
925+
```bash
926+
az vm run-command invoke \
927+
--resource-group <rsc-group> \
928+
--name <vm-name> \
929+
--command-id RunShellScript \
930+
--scripts @script.sh
931+
```
932+
933+
#### SSH Session
934+
935+
When connecting via SSH:
936+
937+
- Runs as a **regular OS user**
938+
- Uses standard network stack
939+
- Does **NOT have agent-level access**
940+
941+
As a result:
942+
943+
- Requests to `168.63.129.16` may fail or return incomplete data
944+
- GoalState may not be accessible
945+
946+
**Script Examples:**
947+
948+
{{#tabs }}
949+
{{#tab name="Linux" }}
950+
951+
```bash
952+
#!/usr/bin/env bash
953+
set -euo pipefail
954+
955+
ws="http://168.63.129.16"
956+
957+
echo "[*] Getting Goal State..."
958+
959+
goal_urls=(
960+
"$ws/?comp=goalstate"
961+
"$ws/machine?comp=goalstate"
962+
"$ws/machine/?comp=goalstate"
963+
)
964+
965+
goal_xml=""
966+
for url in "${goal_urls[@]}"; do
967+
if goal_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$url" 2>/dev/null)"; then
968+
echo "[+] GoalState OK via $url"
969+
break
970+
fi
971+
done
972+
973+
if [[ -z "$goal_xml" ]]; then
974+
echo "[-] No GoalState endpoint responded"
975+
exit 1
976+
fi
977+
978+
ext_url="$(
979+
GOAL_XML="$goal_xml" python3 - <<'PY'
980+
import os
981+
import xml.etree.ElementTree as ET
982+
983+
xml = os.environ["GOAL_XML"].strip()
984+
root = ET.fromstring(xml)
985+
986+
def lname(tag):
987+
return tag.rsplit("}", 1)[-1]
988+
989+
for el in root.iter():
990+
if lname(el.tag) == "ExtensionsConfig" and (el.text or "").strip():
991+
print(el.text.strip())
992+
break
993+
PY
994+
)"
995+
996+
if [[ -z "$ext_url" ]]; then
997+
echo "[-] No ExtensionsConfig URL found in GoalState"
998+
echo "[*] Identity-like nodes seen in GoalState:"
999+
GOAL_XML="$goal_xml" python3 - <<'PY'
1000+
import os
1001+
import xml.etree.ElementTree as ET
1002+
1003+
xml = os.environ["GOAL_XML"].strip()
1004+
root = ET.fromstring(xml)
1005+
1006+
def lname(tag):
1007+
return tag.rsplit("}", 1)[-1]
1008+
1009+
found = False
1010+
for el in root.iter():
1011+
name = lname(el.tag)
1012+
if "Identity" in name:
1013+
found = True
1014+
text = (el.text or "").strip()
1015+
print(f"<{name}>{text}</{name}>")
1016+
1017+
if not found:
1018+
print(" (none)")
1019+
PY
1020+
exit 0
1021+
fi
1022+
1023+
echo "[*] Getting ExtensionsConfig..."
1024+
ext_xml="$(curl -fsS -H "x-ms-version: 2012-11-30" "$ext_url")"
1025+
1026+
EXT_XML="$ext_xml" python3 - <<'PY'
1027+
import os
1028+
import xml.etree.ElementTree as ET
1029+
1030+
xml = os.environ["EXT_XML"].strip()
1031+
root = ET.fromstring(xml)
1032+
1033+
def lname(tag):
1034+
return tag.rsplit("}", 1)[-1]
1035+
1036+
ids = [el for el in root.iter() if lname(el.tag) == "UserAssignedIdentity"]
1037+
1038+
if not ids:
1039+
print("[-] No UserAssignedIdentity nodes found")
1040+
print("[*] Identity-like nodes present in ExtensionsConfig:")
1041+
shown = False
1042+
for el in root.iter():
1043+
name = lname(el.tag)
1044+
if "Identity" in name:
1045+
shown = True
1046+
text = (el.text or "").strip()
1047+
attrs = " ".join(f'{k}="{v}"' for k, v in el.attrib.items())
1048+
if attrs:
1049+
print(f" <{name} {attrs}>{text}</{name}>")
1050+
else:
1051+
print(f" <{name}>{text}</{name}>")
1052+
if not shown:
1053+
print(" (none)")
1054+
raise SystemExit(0)
1055+
1056+
for idnode in ids:
1057+
client_id = ""
1058+
object_id = ""
1059+
resource_id = ""
1060+
1061+
for child in idnode.iter():
1062+
name = lname(child.tag)
1063+
text = (child.text or "").strip()
1064+
if name == "IdentityClientId":
1065+
client_id = text
1066+
elif name == "IdentityObjectId":
1067+
object_id = text
1068+
elif name == "IdentityResourceId":
1069+
resource_id = text
1070+
1071+
print()
1072+
print("[+] Managed Identity:")
1073+
print(f" ClientId : {client_id}")
1074+
print(f" ObjectId : {object_id}")
1075+
print(f" ResourceId : {resource_id}")
1076+
PY
1077+
```
1078+
1079+
{{#endtab }}
1080+
1081+
{{#tab name="Windows" }}
1082+
1083+
```bash
1084+
$ws = "http://168.63.129.16"
1085+
$h = @{
1086+
"x-ms-version" = "2012-11-30"
1087+
}
1088+
1089+
Write-Host "[*] Getting Goal State..." -ForegroundColor Cyan
1090+
1091+
$goalUrls = @(
1092+
"$ws/?comp=goalstate",
1093+
"$ws/machine?comp=goalstate",
1094+
"$ws/machine/?comp=goalstate"
1095+
)
1096+
1097+
$gs = $null
1098+
1099+
foreach ($url in $goalUrls) {
1100+
try {
1101+
$gs = Invoke-WebRequest -Uri $url -Headers $h -UseBasicParsing -ErrorAction Stop
1102+
Write-Host "[+] GoalState OK via $url" -ForegroundColor Green
1103+
break
1104+
} catch {}
1105+
}
1106+
1107+
if (-not $gs) {
1108+
Write-Host "[-] No GoalState endpoint responded" -ForegroundColor Red
1109+
return
1110+
}
1111+
1112+
[xml]$xml = $gs.Content
1113+
$cfg = $xml.GoalState.Container.RoleInstanceList.RoleInstance.Configuration
1114+
1115+
$extUrl = $cfg.ExtensionsConfig
1116+
1117+
Write-Host "[*] Getting ExtensionsConfig..." -ForegroundColor Cyan
1118+
1119+
try {
1120+
$ext = Invoke-WebRequest -Uri $extUrl -Headers $h -UseBasicParsing -ErrorAction Stop
1121+
[xml]$extXml = $ext.Content
1122+
} catch {
1123+
Write-Host "[-] Error getting ExtensionsConfig" -ForegroundColor Red
1124+
return
1125+
}
1126+
1127+
# Extract Managed Identity info
1128+
$ids = $extXml.SelectNodes("//UserAssignedIdentity")
1129+
1130+
if (!$ids) {
1131+
Write-Host "[-] No User Assigned Identities found" -ForegroundColor Red
1132+
return
1133+
}
1134+
1135+
foreach ($id in $ids) {
1136+
$clientId = $id.IdentityClientId
1137+
$objectId = $id.IdentityObjectId
1138+
$resourceId = $id.IdentityResourceId
1139+
1140+
Write-Host "`n[+] Managed Identity:" -ForegroundColor Green
1141+
Write-Host " ClientId : $clientId"
1142+
Write-Host " ObjectId : $objectId"
1143+
Write-Host " ResourceId : $resourceId"
1144+
}
1145+
```
1146+
1147+
{{#endtab }}
1148+
{{#endtabs }}
1149+
1150+
8401151
## Privilege Escalation
8411152
8421153
{{#ref}}
@@ -866,6 +1177,9 @@ Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
8661177
- [https://learn.microsoft.com/en-us/azure/virtual-machines/overview](https://learn.microsoft.com/en-us/azure/virtual-machines/overview)
8671178
- [https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/](https://hausec.com/2022/05/04/azure-virtual-machine-execution-techniques/)
8681179
- [https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service](https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service)
1180+
- [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)
1181+
- [https://learn.microsoft.com/en-us/azure/virtual-machines/run-command](https://learn.microsoft.com/en-us/azure/virtual-machines/run-command)
1182+
- [https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux](https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux)
8691183
8701184
{{#include ../../../../banners/hacktricks-training.md}}
8711185

0 commit comments

Comments
 (0)