Executive Summary

Microsoft Threat Intelligence published an analysis on September 9, 2026 of a campaign running since May 2026, attributed to Storm-3121 — linked to ShinyHunters and Falcon extortion activity — and Storm-3032, a group that splintered from BlackFile and now operates under the Helix extortion banner. The campaign is branded around passkeys, and the most useful sentence in the whole report is Microsoft's own caveat: passkey enrollment is often not the actor's true objective.

That matters for detection teams because it relocates the problem. If you build controls around the word "passkey," you are building controls around a pretext that will be renamed next quarter. The actual intrusion is an adversary-in-the-middle or device-code session theft followed by a very recognisable cloud reconnaissance and exfiltration pattern. The durable detections sit in authentication-method registration events, Microsoft Graph traversal, and SharePoint download telemetry — none of which care what word the caller used on the phone.

AttackAttack Overview

The operation opens outside the enterprise perimeter entirely. Actors call victims on personal phone numbers, impersonating IT helpdesk staff, and manufacture urgency around an expiring configuration: a passkey, multifactor authentication, or single sign-on setting that "must be updated immediately to avoid disruption." The victim then receives an SMS containing a link to a site that closely resembles a legitimate Microsoft sign-in experience.

The pretext is well chosen. Passkey rollouts are genuinely happening in most enterprises right now, they genuinely involve an enrollment step users have not seen before, and they genuinely generate helpdesk traffic. A call about passkey enrollment is plausible in a way that a call about password expiry no longer is. The urgency is doing the same work it has always done; the vocabulary has just been refreshed to match the organisation's actual project backlog.

What sits behind the link is not a passkey enrollment page. It is one of three paths to a session token.

AttackOperator Workflow

In the first path, the victim lands on an adversary-in-the-middle proxy. Microsoft's timeline from one intrusion is tight enough to be worth reading as a clock. At T+0, a sign-in error requires secondary authentication. At T+1 minute, MFA is completed, and error 50140 appears for the keep-me-signed-in interruption. Between T+2 and T+3 minutes, the actor is already enumerating applications, organisational profiles, approval workflows and account controls through the identity portals — "My Sign-Ins" and "My Apps." Between T+11 and T+50 minutes, SharePoint Online and Outlook Web Access attempts begin. The initial sign-in came from the OfficeHome application on an unmanaged device, likely attacker-owned.

In the second path, the actor uses the device code flow after the passkey lure. The victim is persuaded to enter a code on the genuine Microsoft authentication page; that approval issues a token to an attacker-controlled client. This is the same primitive PhishPond has covered before, now reached through a passkey-flavoured front door rather than a Teams invitation.

The third path is the most instructive about planning horizon. In at least one case the actor signed in with previously compromised credentials and satisfied MFA using a PhoneAppOTP method that had already been registered — suggesting the authenticator app was enrolled days before the campaign launched. The phone call was not the beginning of that intrusion. It was the resumption of one.

AttackInfrastructure & Tradecraft

The phishing domains follow a naming convention that is almost courteous in its consistency. Observed examples include `passkeyhelpdesk[.]com`, `secure-passkey[.]com`, `add-passkey[.]com`, `integratedsso[.]com` and `oktasession[.]com`. Targets are addressed through a subdomain carrying their own organisation name — `contoso[.]add-passkey[.]com` — which both personalises the lure and gives the operator clean per-victim segmentation.

Registration frequently runs through the Nicenic registrar, and domains have been observed operational within hours of registration. That window is short enough to defeat reputation-based blocking as a primary control, which is a recurring theme in current phishing infrastructure and the reason the detections below deliberately sit downstream of the lure.

Once inside, the actors establish their own foothold in the identity plane by registering an additional MFA method under their control — a new phone number, authenticator application, or software-based OTP token. The resulting audit artifact is distinctive. Microsoft's example shows a newly added entry with `DeviceTag` set to `SoftwareTokenActivated`, `HashFunction` of `hmacsha1`, and placeholder values of `NO_DEVICE` for `DeviceName` and `NO_DEVICE_TOKEN` for `DeviceToken`. Real user enrollments generally carry real device metadata. Those placeholder strings are a gift.

Reconnaissance is automated. Microsoft describes a purpose-built system written in Node.js driving Microsoft Graph across six categories of endpoint: tenant profile (`/organization`, `/subscribedSkus`, `/licenseDetails`), directory (`/users`, `/groups`, `/members`, `/transitiveMembers`), privilege (`/directoryRoles`, `/roleManagement`, `/authentication/methods`), application (`/applications`, `/servicePrincipals`, `/oauth2PermissionGrants`), repository (`/sites`, `/lists`, `/drives`, `/drive/items`, `/search`) and mailbox (`/messages`, `/mailFolders`, `/attachments`).

Exfiltration targets SharePoint Online and OneDrive for Business, with some intrusions extending into Exchange Online over REST. The user agent on the high-volume collection is `python-httpx`. The pacing is deliberate: fewer than 1,000 files or emails inside any one-hour period, which Microsoft assesses is intended to blend with normal enterprise usage. Campaigns run from several hours to multiple days depending on volume.

DetectionDetection Opportunities

Microsoft's framing of the Graph problem is the single most reusable idea in the report, and it generalises well beyond this actor: a single Graph call rarely looks suspicious, but when one identity, application, or access token systematically traverses multiple tenant resources, evaluates privilege and authentication settings, and then reaches mail, files, attachments or document content, those actions collectively form a reconnaissance-to-exfiltration chain.

That is a detection specification. You are not looking for a bad endpoint; you are looking for breadth of traversal by one actor in a short window. The rule below classifies each Graph request into a reconnaissance category and alerts when one actor touches at least three categories, with at least ten requests across at least six distinct paths, inside thirty minutes.

GraphAPIAuditEvents
| where Timestamp > ago(24h)
| where toint(ResponseStatusCode) between (200 .. 299)
| extend Uri = tolower(RequestUri),
         ActorId = coalesce(AccountObjectId, ServicePrincipalId, ApplicationId),
         Path = tostring(split(tolower(RequestUri), "?")[0])
| extend ReconType = case(
   Uri has "/organization" or Uri has "/subscribedskus", "Tenant",
   Uri has "/users" or Uri has "/groups", "Directory",
   Uri has "/directoryroles" or Uri has "/rolemanagement", "Privilege",
   Uri has "/applications" or Uri has "/serviceprincipals", "Application",
   Uri has "/sites" or Uri has "/drive", "Repository",
   Uri has "/messages" or Uri has "/mailfolders", "Mailbox",
   "Other")
| where ReconType != "Other" and isnotempty(ActorId)
| summarize Requests=count(), Categories=dcount(ReconType), DistinctPaths=dcount(Path)
    by ActorId, IpAddress, ApplicationId, bin(Timestamp, 30m)
| where Requests >= 10 and Categories >= 3 and DistinctPaths >= 6

The authentication-method registration signal is narrower and higher confidence. Watch for a successful user update that increases the count of registered strong-authentication devices, which catches the attacker's persistence step regardless of how they got in.

CloudAppEvents
| where ActionType == "Update user."
| where tostring(RawEventData.ResultStatus) == "Success"
| where RawEventData has_any ("StrongAuthenticationPhoneAppDetail", "StrongAuthenticationUserDetails")
| mvexpand ModifiedProp = RawEventData.ModifiedProperties
| where tostring(ModifiedProp.Name) in ("StrongAuthenticationPhoneAppDetail", "StrongAuthenticationUserDetails")
| extend OldValue = tostring(ModifiedProp.OldValue), NewValue = tostring(ModifiedProp.NewValue)
| extend OldDeviceCount = countof(OldValue, "\"Id\""), NewDeviceCount = countof(NewValue, "\"Id\"")
| where NewDeviceCount > OldDeviceCount
| project Timestamp, AccountDisplayName, IPAddress, OldDeviceCount, NewDeviceCount, NewValue

For the exfiltration stage, the user agent does most of the work. Collection tooling that speaks `python-httpx` against SharePoint and OneDrive has no legitimate counterpart in most tenants.

CloudAppEvents
| where ApplicationId == "20892" or ApplicationId == "15600"
| where ActionType in ("FileDownloaded", "FileAccessed", "SyncDownloadedFull")
| where isnotempty(AccountObjectId)
| where UserAgent has "python-httpx"
| summarize FilesAccessedLastWindow = count()
    by AccountObjectId, IPAddress, ISP, UserAgent, bin(Timestamp, 2h)
| where FilesAccessedLastWindow >= 100

Defender XDR ships named detections covering the same chain, which are worth confirming are enabled and routed: "Malicious registration of a device with strong MFA," "Malicious registration of an attacker controlled MFA device," "Suspicious Entra Graph API query observed," "Automated mass SharePoint/OneDrive file access via python-httpx," and "Malicious sign in from an IP address associated with recognized attacker infrastructure."

ValidationValidation Workflow

Validate from the inside out, in an authorized test tenant with disposable accounts and no production data.

Start with the registration rule, because it is the easiest to prove and the most likely to be silently broken by log configuration. As a test user, add a software OTP authenticator, then confirm a `CloudAppEvents` record appears with the modified-properties structure the rule parses. The device-count comparison is the fragile part of that query — verify that `OldDeviceCount` and `NewDeviceCount` actually populate in your tenant rather than assuming the extraction works. A rule that never fires because a field is empty looks identical to a rule with nothing to catch.

Next, exercise the Graph traversal rule with a benign script. Authenticate as a test identity and issue a handful of reads across at least three of the six categories — `/organization`, `/users`, `/directoryRoles` — staying well inside thirty minutes, then confirm the summarisation crosses the thresholds. Then run the same script with calls spread across two hours and confirm it does not alert. That pair tells you the window is doing what you think it is.

For the exfiltration rule, generate 100-plus file reads from a test account using an HTTP client that sets a `python-httpx` user agent, and confirm the two-hour bucket fires. Then re-run the same volume from a normal browser session and confirm it stays quiet. The gap between those two runs is the entire value of the rule, and it is also, as the next section argues, its weakest point.

GapsEvasion & Gaps

The `python-httpx` signal is precise and trivially defeated. It is a default user-agent string, not a capability, and an operator who changes one line of their collector removes it. Treat it as a high-confidence bonus detection, never as the backbone of your exfiltration coverage. The volume-and-pacing logic underneath it is the part worth investing in — and the actors already know that, which is why they cap collection near 1,000 objects per hour. An operator who halves that rate to stay under your threshold costs themselves time and nothing else.

The Graph traversal rule inherits the opposite problem: legitimate automation looks a lot like reconnaissance. Backup tooling, compliance scanners, SaaS security posture products and your own CMDB sync will all traverse several categories from one service principal in under thirty minutes. The rule is usable only with a maintained allow-list of known service principals, and that allow-list is an evasion surface. An actor who compromises a service principal that is already allow-listed — the Storm-3168 pattern Microsoft documented separately in late September — lands inside the exception rather than the detection.

The registration detection has a narrower gap but a real one. It catches a method being added. It does not catch an actor who registered their method days earlier, before any of the alerting context existed, which is precisely what Microsoft observed in the third access path. Retrospective hunting over authentication-method changes for the ninety days preceding a confirmed compromise is the only coverage for that, and it is a hunt, not an alert.

Finally, none of this touches the phone call. The initial contact happens on a personal device over a channel the enterprise does not log, and no amount of cloud telemetry will produce an alert for it.

Defensive Recommendations

Rank by leverage, and the ordering is unusually clear here because several of these controls remove entire paths rather than detecting them.

Block the device code and authentication transfer flows via Conditional Access, except where an explicit business need exists. This deletes one of the three access paths outright for most of your population.

Enforce Conditional Access requiring a managed, compliant device for Exchange, SharePoint and Graph-privileged applications, and limit unmanaged-device access to web-only sessions with no download or sync. The AiTM path in this campaign ran from an unmanaged, likely attacker-owned device; a compliance requirement stops the token being useful even when the phish succeeds.

Apply strict Conditional Access to security-information registration, with required sign-in frequency set to always. This is the control that directly contests the attacker's persistence step.

Restrict user consent for applications, require admin approval, and review service principals holding high-privilege Graph permissions — `Mail.Read`, `Files.Read.All`, `Directory.Read.All`. This is also what protects your Graph allow-list from becoming a hiding place.

Harden the helpdesk process itself. Verify user identity through a rigorous process before performing any helpdesk-initiated credential or MFA reset, and alert on every such reset. The campaign begins with an impersonated helpdesk; the symmetric defence is making the real helpdesk hard to impersonate.

On confirmed compromise, the response order matters: revoke active sessions and refresh tokens first, then reset credentials, then remove attacker-registered authentication methods. Disabling the account without revoking tokens leaves the attacker holding valid access, which remains the most common way these intrusions survive their own detection.

Enforce phishing-resistant MFA — FIDO2, passkeys, Windows Hello for Business — via Conditional Access. The irony is deliberate and worth stating plainly: the control this campaign impersonates is the control that defeats it. That is not a reason to slow a passkey rollout. It is a reason to make sure users have already completed one, because a user who has enrolled a passkey is a user who knows what the real enrollment looked like.