threat_intelligence1395 wordsRead on Arc Codex

4 million fake applications and one blind spot: A SOC playbook for OAuth client ID spoofing

Two campaigns created 4M+ fake identities to enumerate Microsoft Entra ID accounts — without logins. Learn how to detect the behavior, not the threat. Key takeaways - OAuth client ID spoofing defeats detections that key off application name or a known application ID, because the field itself is fabricated, rotated or blank. - AADSTS700016 paired with an unrecognized client ID can mean valid credentials, not a broken app registration — treat it as a triage signal, not noise. - Effective detection logic correlates client ID cardinality, missing application names and AADSTS error sequences across a time window, not any single event. - A rehearsed response runbook (reset, revoke, review) matters as much as the detection query itself, and the cost of skipping it shows up as account takeover, not just alert fatigue. The signal hiding in plain sight Picture a queue of failed sign-in alerts against Microsoft Entra ID. Each one points to a different application ID. None of the IDs are registered in the tenant. None crosses a volume threshold on its own. Nothing about the queue reads as a coordinated campaign; it reads like the normal debris of expired app registrations and forgotten test scripts that every tenant accumulates. An analyst scanning for a named application spiking in failures would scroll right past this queue without a second look. That is the point. Since December 2025, at least two threat actors have built enumeration campaigns specifically engineered to look like configuration noise instead of an attack, and the difference between the two is a detection-engineering question, not just a threat-intel one. The mechanism, in three error codes The technique abuses the OAuth 2.0 Resource Owner Password Credentials (ROPC) flow, in which a single token request bundles a username, password, and client ID. Three Entra ID response codes carry the signal that matters. AADSTS50034 means the username doesn’t exist. AADSTS50126 means the username exists, but the password is wrong. AADSTS700016 fires when the username and password are both valid but the client ID isn’t recognized. That last code is the one worth building a detection around: on its own, it looks like an app-registration problem an admin forgot to clean up. When paired with an unfamiliar, constantly rotating client ID, it can mean an attacker has already confirmed a working credential pair and is one step away from account takeover. ROPC persists in more environments than security teams expect. It survives in legacy scripts, CI pipelines and third-party integrations built before Microsoft’s guidance to avoid it, precisely because it is the path of least resistance for developers who never need to open a browser. That installed base is what gives the technique room to operate. Attackers are not exploiting a rare misconfiguration; they are exploiting a flow that is still common enough to hide inside. Sunil Gentyala Two campaigns, one detection gap UNK_pyreq2323. Proofpoint tracked this campaign running from AWS infrastructure, mutating the trailing digits of a real Exchange Online app ID across more than 700,000 variations, reusing each one against no more than a dozen accounts before discarding it. The campaign targeted over a million users across roughly 4,000 tenants, with about 28 percent of touched accounts hitting lockout. UNK_OutFlareAZ. Running mostly through Cloudflare, this campaign skipped mutation entirely and generated a fresh random UUID per request: 3.7 million spoofed IDs against more than two million accounts, peaking near 720,000 targeted users on March 15, 2026. Proofpoint’s own researchers say they can’t yet confirm whether this is one actor or two independent clusters converging on the same trick through forum discussion and trial and error, a point Help Net Security also covered in reporting on the research. For detection purposes, that ambiguity doesn’t matter. Both campaigns defeat the same control, per-application volume thresholds, using the same gap: an uncorrelated, disposable identifier field that nobody is watching for cardinality. Why this is a CISO problem, not just a queue problem A 28 percent lockout rate across a million-user campaign is not a rounding error. It is a helpdesk cost, a productivity hit and, for every account where AADSTS700016 fired before the lockout, a live credential an attacker has already validated and can use elsewhere: mailbox access, OAuth consent grants, lateral movement into connected SaaS. The detection gap here isn’t cosmetic. It is the difference between catching a validated-credential event while it’s still isolated to one authentication attempt and finding out about it three weeks later from a mailbox-forwarding rule or a fraudulent wire request. That is what makes this worth a rule, not just a footnote in a threat-intel digest. Building the detection A workable rule reconstructs the request sequence instead of scoring one event in isolation. Over Entra ID sign-in logs, or the equivalent SIEM ingestion, the core logic looks like this: SigninLogs | where TimeGenerated > ago(1h) | where ResultType in ("50034", "50126", "700016") or isempty(AppDisplayName) | summarize DistinctClientIDs = dcount(AppId), ResultCodes = make_set(ResultType), Usernames = make_set(UserPrincipalName) by SourceIPAddress, UserAgent, bin(TimeGenerated, 15m) | where DistinctClientIDs > 5 | where ResultCodes has "700016" The two variables that matter most are DistinctClientIDs, because a single source cycling through many unregistered app IDs is the tell that per-application thresholds miss, and the presence of AADSTS700016 in that same window, which elevates the event from configuration noise to possible credential validation in progress. Layer in username-pattern detection, alphabetic or dictionary progression across attempts from the same source, to catch the OutFlareAZ-style wordlist pattern specifically. Tune the DistinctClientIDs threshold against your own tenant’s baseline before trusting it in production. A dev team running CI against a handful of test app registrations can produce a smaller version of the same shape, and Conditional Access policies scoped only to named applications will not catch a fabricated client ID that never matches an intended application scope in the first place. Wire the rule into existing SOAR or ticketing workflows rather than a standalone dashboard nobody checks on a Friday afternoon. A detection that fires into the same queue as password-spray and impossible-travel alerts gets triaged with the same urgency; one that lands in an isolated identity-hygiene report gets read weeks later, if at all. Separating signal from noise Not every blank-app-name or 700016 event is an attack. Deleted app registrations, expired multi-tenant consent, wrong-tenant endpoint calls and CI pipelines pointed at the wrong client ID all produce similar-looking noise. Two filters cut the false-positive rate substantially. Legitimate misconfiguration is almost always low-cardinality, one or two client IDs, not dozens, and it repeats from known internal infrastructure. The attack pattern is high-cardinality and arrives from unfamiliar ASNs or hosting-provider ranges. Cross-referencing against existing password-spray and impossible-travel alerts before escalating avoids paging an analyst for a stale service principal, and tracking the rule’s precision over its first few weeks, confirmed incidents versus total fires, is worth doing before treating it as production-grade. Document the triage criteria alongside the rule itself, since the next analyst on shift needs the same reasoning to close a ticket with confidence rather than escalate out of caution. The response runbook When the correlated signal fires, the runbook should be short and rehearsed before it’s needed, not improvised during the incident. Force a password reset on the affected account, revoke active sessions and refresh tokens, and review sign-in and mailbox-access activity for the days following the AADSTS700016 hit, since that is the window where a confirmed credential gets used elsewhere. Retiring ROPC entirely, migrating interactive apps to browser-based auth and service workloads to managed identities or certificate-based service principals, removes the flow this technique depends on. Until that migration is complete, the detection above is what stands between a ticket closed as configuration noise and a caught account-takeover attempt. The broader lesson for detection engineers The reusable lesson here isn’t about OAuth specifically. Any field a defender logs but doesn’t correlate — application ID, user agent, ASN, whatever comes next — is a candidate for an attacker to fragment their traffic across until per-field thresholds stop tripping. Four million fake applications is what that gap looks like at scale when nobody is watching cardinality instead of volume, and it is the same underlying playbook as MITRE ATT&CK’s Valid Accounts: Cloud Accounts, T1078.004: an attacker using technically valid credentials. The fix generalizes regardless of which field gets exploited next. Build detections around behavioral sequences across multiple fields and a time window, not a single field crossing a static threshold.

How it works

Once you click Generate, Ollama reads this article and crafts 5 comprehension questions. Your answers are graded against the article content — general knowledge won't be enough. Score 70+ to count toward your certificate.

Questions are cached — you'll always get the same 5 for this article.