Anatomy of an Okta Account Takeover

Avatar photo
Hadar Waldman July 14, 2026

An adversary-in-the-middle relay let the attacker clear MFA as a valid user; they enrolled their own phone as a trusted factor and deleted the two security emails that would have exposed them. The proof was sitting in a different log source.

TL;DR — An attacker phished an employee’s password and a live MFA code through a real-time relay and logged into their Okta account, cleared the HIGH-risk MFA challenge as a valid user, enrolled their own device for persistence, and used the SSO session to read mail and delete the security alerts that would have exposed them. MFA worked as designed; the attacker just held both factors. Okta, Entra, and the customer’s SIEM each saw a fragment and none saw the compromise. Artemis’s continuous cross-source hunting caught it, joining the Okta login to the Microsoft 365 audit trail through a single shared session ID, reconstructing the whole intrusion, including the evidence the attacker deleted.

What normal looked like

The account we’ll follow belongs to an ordinary employee with no admin roles. Their telemetry is boring in the way defenders like: one primary device fingerprint carrying 158 events over 30 days, a handful of known IPs, logins from Cleveland. On the afternoon of day zero, at 16:31 UTC, they started a session exactly like that, known device, known IP, Okta risk LOW.

Two details from this quiet baseline matter later. First, Okta had not seen a factor-enrollment event on this account in its 30-day history; whatever MFA the employee used, they set it up long ago. Second, when Okta sees a new device or a new security method, it emails the account owner. That notification path, the mechanism designed to make the user themselves the last line of defense, is the thing the attacker went after.

There may also have been a warning shot. Two days earlier, the customer’s SIEM flagged a failed self-service password reset against this account from an unfamiliar IP. It logged it as a standalone alert; nobody connected it to anything, because at that point there was nothing to connect it to. It never surfaced in the Okta logs we hold, so we can’t tie it to the same actor. But a failed reset probe two days before a takeover is the kind of loose thread that only looks like a lead in hindsight.

Sixteen minutes later, a device the account had never seen

Okta’s behavioral engine did its job at that moment. The policy.evaluate_sign_on event that challenged the login carries Okta’s own risk verdict inline:

At 16:47 UTC, sixteen minutes into the employee’s legitimate Cleveland session, authentication attempts against the same account began from Las Vegas. Two failed with invalid credentials. The password landed on the third try at 16:48:32.

"risk": { "level": "HIGH", "reasons": "Anomalous Location, Anomalous Device" },
"behaviors": {
  "New Geo-Location": "POSITIVE",
  "High Velocity":    "POSITIVE",
  "New Device":       "POSITIVE",
  "New ASN":          "POSITIVE",
  "New IP":           "POSITIVE",
  "New State":        "POSITIVE",
  "New City":         "POSITIVE",
  "New Country":      "NEGATIVE"
}

Seven of eight behavioral heuristics fired POSITIVE — the login was new on geolocation, device, ASN, IP, state, city, and impossibly fast relative to the Cleveland session. Only “New Country” stayed negative, because the attacker’s residential proxy kept them inside the US. Okta rated it HIGH and emailed the employee a new-device notification. But two other fields in the same event told the fuller story. The IP itself looked unremarkable — it resolved to a consumer ISP, and Okta’s ML reputation model set threatSuspected: false. What gave it away was associatedProxies:

"associatedProxies": ["OXYLABS_PROXY", "RAYOBYTE_PROXY"],
"threatSuspected": false

The login IP belonged to two commercial residential-proxy pools, the kind sold by the gigabyte precisely because they make attacker traffic indistinguishable from home broadband — invisible to reputation scoring, which is why threatSuspected came back false even as the behavioral engine screamed.

MFA was not skipped. Okta’s sign-on policy saw the HIGH risk and did the right thing: it challenged for a second factor. And the attacker answered it. Ninety seconds after the password, at 16:49:52, a valid Okta soft-token one-time code cleared the challenge from the attacker’s own IP. The attacker was holding a live, correct TOTP for an account whose factors were set up long ago and which they had never touched, and they produced a second valid code a minute later. A code like that has a thirty-second shelf life; having a fresh one on demand, from residential-proxy infrastructure, is consistent with an adversary-in-the-middle relay: a proxy phishing page sitting between the victim and the real Okta, capturing the password and each current code and replaying them while they’re still valid. We never see that page. The capture happens off-platform, on the attacker’s own infrastructure, and leaves no event in Okta or anywhere else we collect. What the logs do show is the result: MFA held exactly as designed, and it verified an attacker who had phished the second factor along with the first. The session carried a device fingerprint with zero prior history on the account, in an environment where the employee’s real device had 158 events.

Relayed access is fragile, it dies with the phished session, and the next login needs another live code. So the attacker made it durable. Four minutes after the password, at 16:52:02, they enrolled a new device in Okta Verify and activated three MFA factors (TOTP, push, and signed nonce) in a single second. This time the enrollment came from a different IP than the login, and Okta’s own securityContext named the service:

"securityContext": {
  "isProxy": true,
  "ipDetails": { "ipServiceCategories": [
    { "type": "VPN", "operator": "MULLVAD_VPN", "isAnonymous": true }
  ]}
}

Mullvad VPN, isProxy: true – a commercial anonymizing service, a separate hop from the residential-proxy pool used for the login. The enrolled device was an unmanaged iPhone whose self-reported name was “Miley’s iPhone”, which is not the name of the employee. The attacker who forgot to rename their phone, or chose a throwaway name, left the most human artifact in the whole log trail, and with it, persistent MFA control of the account: from this moment, push challenges went to the attacker’s pocket, no relay required.

Four minutes inside Microsoft 365

Everything up to this point lives in Okta’s system log. What happened next doesn’t.

Between 16:50 and 16:53 the attacker used their session to SSO into Microsoft 365. The M365 unified audit log picks up the same session (the AADSessionId on the Office activity matches the session minted for the attacker’s SSO) and records what they did across a thirty-three-second window, 16:53:51 to 16:54:24 UTC:

OrderOperationCountTarget
1MailItemsAccessed×3The victim’s mailbox
2FilePreviewed×4SharePoint documents owned by four other employees (a candidate CV, invitee lists, event schedules, expansion-planning material)
3MoveToDeletedItems×2“New sign-on notification”, “Security method enrolled”
4SoftDelete×4The same two notifications purged from Deleted Items, plus an older Okta email swept along

The deletions came in a deliberate two-step pattern: move each Okta notification from the inbox to Deleted Items, then purge it from Deleted Items so it’s gone from the recover-deleted-items view too. The raw audit rows show it plainly — same client IP as the login, subjects naming the exact Okta emails, and Okta’s own @okta.com message IDs:

{ "Operation": "MoveToDeletedItems", "CreationTime": "16:54:02", "ClientIP": "<login IP>",
  "AffectedItems": [{ "Subject": "Security method enrolled", "InternetMessageId": "<...@okta.com>" }],
  "Folder": { "Path": "\\Inbox" } }

{ "Operation": "SoftDelete", "CreationTime": "16:54:24", "ClientIP": "<login IP>",
  "AffectedItems": [{ "Subject": "New sign-on notification", "InternetMessageId": "<...@okta.com>" }],
  "Folder": { "Path": "\\Deleted Items" } }

Out of an entire mailbox, the deletion operations targeted the two messages that would have tipped off the victim: the Okta email announcing a new sign-on, and the Okta email announcing a new security method. Both had landed in the inbox minutes earlier, triggered by the attacker’s own login and enrollment.

It nearly worked. IT wiped all five MFA factors in one action, and the employee re-enrolled from a known IP with a phishing-resistant factor. The attacker came back to the door twice more, failed both times, and gave up.

Day / Time (UTC)What happenedATT&CK
Day −2The incumbent SIEM flags a failed self-service password reset from an unfamiliar IP — not corroborated in Okta, possibly the same actorT1110
Day 0, 16:31Employee’s legitimate session from Cleveland: known device, known IP, LOW risk
16:47Login attempts from residential-proxy IPs on a never-seen device fingerprint; two credential failuresT1090.002 · T1110
16:48Password accepted; Okta scores it HIGH risk (seven of eight behavioral checks positive) and challenges for MFAT1078.004
16:49:52Valid Okta soft-token one-time code, typed from the attacker’s IP, clears the MFA challenge — consistent with a real-time relayT1621 · T1111
16:50–16:53Attacker SSOs into the Okta dashboard and Microsoft 365 (three SSO events)T1078.004
16:52“Miley’s iPhone” enrolled via Mullvad VPN; three MFA factors activated in one second — relayed access made persistentT1098.005
16:53–16:54Same session in M365: three mailbox reads, four SharePoint file previews of other employees’ documentsT1114.002 · T1213.002
16:54Attacker soft-deletes the two Okta security-notification emails from the victim’s mailboxT1070.008
18:52IT wipes all five MFA factors on the account in one action; the employee re-enrolls minutes later from a known IP with a phishing-resistant factor
22:16–23:09Attacker retries twice from the proxy infrastructure, fails, abandons — locked out
DetectionArtemis’s cross-source hunting links the Okta login to the M365 session and surfaces the full intrusion — the detection point

Why every tool saw only half of the picture

Walk the vendor stack and count what each one knew.

Okta scored the initial login HIGH risk, challenged for MFA, and sent the emails — it did its job. But the challenge was answered with a valid password and a live one-time code, so a correct policy still admitted an attacker who had phished both factors. Its IP-reputation model rated the proxy address low risk, and a risk-scored login event is one row among thousands — nothing in Okta connects a Tuesday login to what a session did inside Microsoft 365 an hour later.

Entra ID Protection scored all eleven attacker sign-ins as no risk at all: riskLevelDuringSignIn empty, riskState empty, conditional access not applied. This is a structural gap, not a tuning problem. In a federated tenant, Okta authenticates the user and hands Microsoft a signed assertion; Entra’s risk evaluation largely never engages. The identity provider that owned the compromised session and the productivity suite where the damage happened each assumed the other had it.

The customer’s SIEM had, over the surrounding days, three separate alerts touching this user: a medium-severity anomalous-ASN login, a low-severity unusual-SharePoint-operations alert, and the failed password reset from two days before. Three fragments, none urgent on its own, no join between them. Each was technically correct. None of them was the incident.

And the mechanism designed to catch exactly this case — tell the human whose account it is — was defeated by the attacker in under a minute, using access the compromise itself had granted.

How Artemis caught it

Artemis continuously hunts across customer telemetry, sweeping identity and privilege events over Okta and Entra against each actor’s own baseline. This account tripped it: a HIGH-risk impossible-travel login, a first-ever factor enrollment, and a full factor wipe on an account that had never seen an enrollment. Artemis opened a case and its investigation did the thing a point tool can’t — it followed the attacker’s session across the vendor boundary. The session ID Okta minted at SSO appears on every one of the Microsoft 365 audit records, and that join is what turned “suspicious login” into “confirmed takeover, mailbox read, four employees’ documents exposed, security alerts deliberately deleted.” The cover-up became the evidence: the deletions the attacker ran to hide the compromise are themselves audit events, stamped with their session.

The account was contained the same day, but containment answers “is the attacker out?”, not “what did they touch?”. The second question can only be answered when the Okta and M365 trails are joined. The customer closed the case as a true positive: real compromise, no lateral movement, and, for the first time, a complete inventory of what the attacker actually reached.

The pattern behind the incident

Nothing in this attack is exotic, and that’s the point. Residential proxies defeated IP reputation. A real-time relay defeated MFA, not by breaking it but by phishing the password and the one-time code together and replaying both while they were still valid. It’s the same adversary-in-the-middle pattern behind a wave of Okta account takeovers over the past two years. Attacker-controlled MFA enrollment then turned the second factor from a hurdle into a persistence mechanism. Federation quietly disabled a second risk engine the organization probably believed was watching. No defensive layer was broken; each one was handed traffic in a form it wasn’t built to question. And no single layer saw the whole picture: the compromise existed only where the Okta trail and the M365 trail met. That join is one no point tool makes on its own, and it’s what turns a scattering of ‘probably fine’ events into a documented intrusion.

For defenders

Three things you can act on today, whatever stack you run, drawn from where this attack was actually catchable.

  1. Require phishing-resistant factors, because a challenge alone isn’t protection. MFA fired here and was answered. The attacker phished the one-time code along with the password and relayed both in real time, which is exactly what adversary-in-the-middle kits are built to do. Any factor the user can read off a screen, the attacker can read too. FIDO2, passkeys, and Okta FastPass close this off because the credential is bound to the origin: it simply won’t authenticate to a phishing page. If a high-risk sign-on can be satisfied by a typed code, assume a determined attacker can phish it
  2. Alert on MFA factor enrollment in context, not just login risk. A new factor enrolled within minutes of a first-seen device fingerprint, from anonymizing infrastructure, on an account with no enrollment history is a high-fidelity intersection even when each element alone is noise.
  3. Audit what federation does to your risk engines. If a third-party IdP fronts your Microsoft tenant, Entra ID Protection may be evaluating little or nothing on federated sign-ins. Know which engine actually scores each hop, ship both sides’ logs to one place, and correlate on the session identifiers that cross the boundary; they are the only thread that ties an IdP login to what the session did downstream.

Detection as a prompt

No single log answers “was this account taken over and what did they touch.” The Okta side is a login-risk-and-factor-mutation hunt; the mailbox and file activity that reveal intent live in Microsoft 365, joined to the login only by a shared session id. Artemis expresses threat hunts as instructions to an investigation agent rather than as static rules, which is what let this case pivot from Okta into M365 mid-investigation. If you run an agentic investigation tool, the same idea ports directly. A prompt that would have surfaced this incident:

Role: identity-takeover triage over the org's own logs
(IdP/SSO sign-on + factor events, M365/Workspace audit, mail, files).

Trigger: a HIGH-risk or impossible-travel sign-on that still SUCCEEDS,
OR a new MFA factor enrolled on an account with no enrollment history in
the last 30-90 days, from anonymizing infrastructure (proxy/VPN tags).

Investigate, in order, and report what you find at each step:
  1. Was the challenge answered, not skipped? Confirm which factor
     satisfied MFA and the source IP it came from. A password + one-time
     code cleared from the SAME IP as a first-seen device, especially a
     second valid code minutes later, is the shape of a real-time relay,
     not an accidental push tap. State which.
  2. Persistence. Was a new device/factor enrolled shortly after the
     login, from a different or anonymizing IP? Is the enrolled device
     unmanaged, and does its name match the account owner?
  3. One session across sources. Take the session id the IdP minted at
     SSO and find it in the M365/Workspace audit log. What did that same
     session do: mail read, files previewed/downloaded, forwarding rules,
     items deleted? This join is the whole ballgame.
  4. Cover-up as signal. Look for deletion of the IdP's own security
     notifications (new-device, new-factor emails) shortly after the
     events that generated them. The deletion is itself an audit event.
  5. Scope. Whose data did the session touch beyond the victim's own
     mailbox? List other users' files/mailboxes accessed.

Do not treat a passed MFA challenge, a valid session, or a factor the
"user" enrolled as exoneration. The question is not whether each step was
permitted; it is whether this sequence is normal for this identity, and
what the session did once inside.

Point it at the logs a company already has, and it walks the same path Artemis did: confirm the login was really answered, follow the factor enrollment, then join the one session across IdP and productivity-suite logs to decide scope.

Details are drawn from real findings in a customer environment. Details are anonymized and indicators sanitized.

Table of contents
    Get a personalized demo

    Ready to See Everything and Stop Anything?

    Book a personalized demo and see what Artemis is building differently and how it can anticipate anything in your environment.

    Book A Demo
    2
    B+
    events processed every hour
    15,000
    + TB
    data processed daily
    2,000
    +
    insights generated daily