
A real-time relay let an attacker clear MFA as a valid user. They enrolled their own phone as a trusted factor, then deleted the two emails that would have exposed them. The proof was sitting in a different log source.
An attacker phished an employee’s password and a live MFA code through a real-time relay, then logged into their Okta account.
Okta scored the login HIGH risk and challenged for a second factor. The attacker answered it, because they were holding both. From there they enrolled their own iPhone as a trusted MFA factor for persistence, SSO’d into Microsoft 365, read the victim’s mailbox, previewed four other employees’ documents, and deleted the Okta security alerts that would have tipped the victim off.
MFA worked exactly as designed. The attacker simply held both factors.
Okta, Entra, and the customer’s SIEM each saw a fragment. 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, and reconstructing the whole intrusion, including the evidence the attacker deleted.
The expected behavior
The account 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 Chicago. 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 that quiet history matter later.
First, Okta had not seen a factor-enrollment event on this account in its entire 30-day history. Whatever MFA the employee used, they had 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 exactly what 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
At 16:47 UTC, sixteen minutes into the employee’s legitimate Chicago session, authentication attempts against the same account began from Los Angeles. Two failed with invalid credentials. The password landed on the third try, at 16:48:32.
Okta’s behavioral engine did its job. The policy.evaluate_sign_on event that challenged the login carries Okta’s own risk verdict inline:
"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"
}Code language: JSON / JSON with Comments (json)
Seven of eight behavioral heuristics fired POSITIVE: new on geolocation, device, ASN, IP, state, and city, and impossibly fast relative to the Chicago session. Only “New Country” stayed negative, because the attacker’s residential proxy kept them inside the US. Okta rated the login 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": falseCode language: JavaScript (javascript)
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 was screaming.
MFA was not skipped. It was answered.
Okta’s sign-on policy saw the HIGH risk and did the right thing: it challenged for a second factor.
And the attacker cleared 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. They were holding a live, correct TOTP for an account whose factors were set up long ago and which they had never touched. A minute later, they produced a second valid code.
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.
Making it durable
Relayed access is fragile. It dies with the phished session, and the next login needs another live code. So the attacker made it permanent.
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 }
]}
}Code language: JavaScript (javascript)
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 “Riley’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 entire 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:
| Order | Operation | Count | Target |
|---|---|---|---|
| 1 | MailItemsAccessed | ×3 | The victim’s mailbox |
| 2 | FilePreviewed | ×4 | SharePoint documents owned by four other employees (a candidate CV, invitee lists, event schedules, expansion-planning material) |
| 3 | MoveToDeletedItems | ×2 | “New sign-on notification”, “Security method enrolled” |
| 4 | SoftDelete | ×4 | The 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, with the 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" } }Code language: JSON / JSON with Comments (json)
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.
| Day / Time (UTC) | What happened | ATT&CK |
|---|---|---|
| Day −2 | The incumbent SIEM flags a failed self-service password reset from an unfamiliar IP. Not corroborated in Okta, possibly the same actor | T1110 |
| Day 0 · 16:31 | Employee’s legitimate session from Chicago: known device, known IP, LOW risk | |
| 16:47 | Login attempts from residential-proxy IPs on a never-seen device fingerprint; two credential failures | T1090.002 · T1110 |
| 16:48 | Password accepted. Okta scores it HIGH risk (seven of eight behavioral checks positive) and challenges for MFA | T1078.004 |
| 16:49:52 | Valid Okta soft-token one-time code, from the attacker’s IP, clears the MFA challenge. Consistent with a real-time relay | T1621 · T1111 |
| 16:50–16:53 | Attacker SSOs into the Okta dashboard and Microsoft 365 (three SSO events) | T1078.004 |
| 16:52 | “Riley’s iPhone” enrolled via Mullvad VPN. Three MFA factors activated in one second: relayed access made persistent | T1098.005 |
| 16:53–16:54 | Same session in M365: three mailbox reads, four SharePoint previews of other employees’ documents | T1114.002 · T1213.002 |
| 16:54 | Attacker soft-deletes the Okta security-notification emails from the victim’s mailbox | T1070.008 |
| Detection | Artemis’s cross-source hunting links the Okta login to the M365 session and surfaces the full intrusion. The detection point. | |
| 18:52 | IT 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:09 | Attacker retries twice from the proxy infrastructure, fails, abandons. Locked out |
Why every tool saw only a fragment
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 every attacker sign-in 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, and 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, across 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 alone, 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 history. This account tripped it three ways: 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. Then 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. 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?” And that second question only closes when the Okta and M365 trails are joined. The customer closed the case a true positive: real compromise, no lateral movement, and, for the first time, a complete account of what the attacker actually reached.
What this means
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. And 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 one saw the whole. The compromise existed only where the Okta trail and the M365 trail met: the join no point tool makes on its own, and the one that 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. A challenge alone isn’t protection. MFA fired here, and it was answered. The attacker relayed a valid one-time code in real time, which is exactly what adversary-in-the-middle kits are built to do. Password-plus-OTP challenges are replayable. FIDO2, passkeys, and Okta FastPass are bound to the origin and don’t relay. If a high-risk sign-on can be satisfied by a code the user reads off a screen, 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 tying 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.
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.
Details are drawn from real findings in a customer environment. Details are anonymized and indicators sanitized.
To protect your organization from the full spectrum of machine-speed attacks see Artemis in action today: https://artemissecurity.com/demo/