Microsoft disclosed in September 2026 that a threat actor tracked as Storm-3068 compromised a single Azure AD identity through self-service password reset (SSPR) abuse, then pivoted through Azure DevOps pipelines to steal Kubernetes cluster credentials — ultimately exfiltrating seven kubeconfig files and gaining access to more than 50 downstream resources from one initial account takeover. No password spray, no zero-day: just SSPR, MFA re-registration, and pipeline permissions that were already too broad. This is the exact attack chain and the controls that close each step.
The Attack Chain, Step by Step
Storm-3068’s campaign worked because each individual step abuses a legitimate feature, not a vulnerability:
- SSPR takeover — The attacker completed a self-service password reset on a target user account, most likely after obtaining the account’s recovery information through a prior phishing or credential-stuffing attempt elsewhere.
- Device registration — Immediately after the password reset, the attacker registered an attacker-controlled device and onboarded it into Microsoft Intune using the now-compromised identity, giving the device a trusted posture in the tenant.
- MFA hijack — The attacker registered their own MFA method on the account and removed the legitimate user’s MFA method, converting a one-time credential theft into persistent, re-authenticatable account control.
- Azure DevOps reconnaissance — Using the now fully-controlled identity, the attacker enumerated Azure DevOps repositories, pipelines, deployment environments, and connected service resources — all through normal, logged, authenticated API calls.
- Pipeline weaponization — The attacker created or modified Azure DevOps pipelines to run a Kubernetes agent step, using the pipeline’s own service connection permissions to pull
kubectl config view --rawoutput and commit the resulting kubeconfig files into a repository. One malicious pipeline alone was authorized to touch more than 50 resources. - Tooling for persistence — The attacker modified pipelines further to deploy the Atera remote monitoring agent (legitimate RMM software, abused for remote access) and Chisel (a TCP/UDP tunneling tool), using Chisel to proxy Kubernetes API traffic back to attacker infrastructure.
Why this matters more than a typical phishing writeup: every step after the initial SSPR abuse used only legitimate product features — Intune enrollment, pipeline authoring, service connections. There’s no malware signature to catch. The defense has to be permission boundaries and anomaly detection, not endpoint protection.
Fix 1: Lock Down Self-Service Password Reset
The entry point is SSPR completed without device or location context. Require a trusted signal before SSPR can succeed:
Entra Admin Center → Protection → Conditional Access → New Policy
Name: Require Compliant Device for SSPR
Assignments:
Users: All users
Target resources: User flows → Self-service password reset
Conditions:
Device platforms: Any (do not exclude)
Grant:
Require device to be marked as compliant
OR Require Microsoft Entra hybrid joined device
Enable policy: On
If your organization can’t mandate compliant devices for all users, scope this policy to admin roles and service-account owners first — those are the identities whose compromise lets an attacker reach Azure DevOps and production infrastructure.
Fix 2: Alert on MFA Method Changes, Not Just Sign-Ins
Storm-3068’s persistence step — removing the legitimate MFA method and registering a new one — is the highest-signal, lowest-noise detection point in the whole chain. Most tenants log this event but don’t alert on it.
// Microsoft Sentinel / Log Analytics
AuditLogs
| where TimeGenerated > ago(1d)
| where OperationName in ("User registered security info", "User deleted security info", "Admin deleted security info")
| extend TargetUser = tostring(TargetResources[0].userPrincipalName)
| extend ActorUser = tostring(InitiatedBy.user.userPrincipalName)
| where TargetUser == ActorUser // self-service change, not admin-initiated
| project TimeGenerated, OperationName, TargetUser, Result,
IPAddress = tostring(parse_json(tostring(AdditionalDetails))[0].value)
Pair this with a rule that fires when an MFA method deletion is followed within minutes by a new method registration from a different IP or device fingerprint than the account’s historical baseline — that sequence is exactly what converts a one-time SSPR compromise into standing access.
Fix 3: Audit and Scope Azure DevOps Service Connections
The pipeline-to-Kubernetes pivot only works because service connections are frequently granted broader scope than any single pipeline needs. List what each pipeline can actually touch:
# List all service connections in a project
az devops service-endpoint list \
--org https://dev.azure.com/<your-org> \
--project <project-name> \
--query "[].{name:name, type:type, authScheme:authorization.scheme, allPipelines:isShared}" \
-o table
# Check which pipelines are authorized to use a given connection
az devops service-endpoint show \
--org https://dev.azure.com/<your-org> \
--project <project-name> \
--id <service-connection-id> \
--query "serviceEndointProjectReferences"
Harden the settings that matter most:
- Disable “Grant access permission to all pipelines” on every Kubernetes/cluster service connection — require explicit per-pipeline authorization instead.
- Scope each connection to one cluster/namespace, not a subscription-wide or multi-cluster service principal. A pipeline that deploys to
stagingshould not hold credentials that can readproductionkubeconfig. - Require pipeline YAML to come from a protected branch (
Approvals and checks → Branch control) so an attacker with repo write access can’t redefine what a trusted pipeline does.
Fix 4: Rotate and Restrict Kubernetes Credentials at the Source
Even with pipeline scoping, assume any kubeconfig that ever transited a CI/CD system should be treated as short-lived, not standing:
# Prefer workload identity federation over long-lived kubeconfig secrets
az aks update \
--resource-group <rg> \
--name <cluster-name> \
--enable-oidc-issuer \
--enable-workload-identity
# Audit existing service account tokens for long expiry
kubectl get secrets --all-namespaces \
-o json | jq -r '.items[] | select(.type=="kubernetes.io/service-account-token") | .metadata.namespace + "/" + .metadata.name'
Projected, short-lived service account tokens (the Kubernetes default since 1.24+) expire in an hour by default — a kubeconfig stolen via a malicious pipeline run is far less useful to an attacker than a long-lived static credential. For the broader pattern of replacing long-lived cluster credentials with certificate-based workload identity, see our mTLS Certificate Authentication for Kubernetes Microservices guide. If your cluster still issues long-lived service account secrets for CI/CD, migrating off them is the first priority.
Fix 5: Hunt for the Persistence Tooling
Atera and Chisel are both legitimate tools being abused here, which means signature-based AV won’t flag them. Hunt for their network and process fingerprints directly:
// Flag non-browser outbound connections from build agents
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where DeviceName has_any ("build", "agent", "runner", "devops")
| where RemoteUrl has "atera.com"
or (InitiatingProcessFileName !in~ ("chrome.exe","msedge.exe","svchost.exe")
and RemotePort !in (443, 80)
and Protocol == "Tcp")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, RemoteUrl, RemotePort
Any hit on a self-hosted Azure DevOps agent or build runner warrants immediate isolation — neither Atera nor Chisel has a legitimate reason to run inside a CI/CD execution environment.
Incident Response Checklist
If you find evidence of this attack chain in your tenant, Microsoft’s containment guidance comes down to five actions, in order:
- Disable the compromised identity and force a credential reset through an out-of-band channel (not SSPR).
- Remove all attacker-registered MFA methods and devices — don’t just reset the password; the attacker’s second factor is the real persistence mechanism.
- Revoke and reissue every credential that touched an affected pipeline — kubeconfig files, service connection secrets, and any PATs (personal access tokens) the compromised identity could have generated.
- Audit and delete unauthorized pipelines, pipeline modifications, and service connections created or altered during the compromise window.
- Rotate cluster-level credentials (not just the stolen kubeconfig) if there’s evidence the attacker reached the Kubernetes API directly — assume the kubeconfig’s embedded certificate or token was used, not just copied.
This campaign is a reminder that identity-layer hardening — SSPR, MFA method integrity, and pipeline permission scoping — now sits upstream of Kubernetes security, not alongside it. For the OAuth-specific variant of credential theft through social engineering rather than pipeline abuse, see our OAuth Device Code Phishing defense guide; for broader non-human credential hygiene across CI/CD systems, see NHI Secrets Sprawl.





