Writing
Anatomy of an Identity Incident

The Push Notification Was Never the Real Problem

Anatomy of an Identity Incident — Uber, September 2022. Everyone remembers the MFA fatigue. The breach that mattered happened one layer deeper, in a script no one owned.

Michael AbramovichOctober 5, 20226 min read

Forensic write-ups of public identity breaches, strictly at the identity layer. No vendor is the villain — the point is the structural class of failure, not the logo on it.

The 60-second version

In September 2022, an attacker got into Uber by buying a contractor's corporate password — most likely harvested by infostealer malware — and then defeating that account's multi-factor authentication not with a technical exploit but with persistence and a lie. They triggered a flood of MFA push notifications and, when that didn't immediately work, messaged the contractor on WhatsApp claiming to be Uber IT, saying the prompts would stop if they just approved one. The contractor approved one.

That got the attacker a foothold. What turned a foothold into a near-total internal compromise was something quieter: a PowerShell script, sitting on an internal network share, with hardcoded credentials for a domain admin account to Uber's privileged access management (PAM) system. The MFA fatigue is what everyone remembers. The plaintext key to the vault is the part worth your attention.

The identity timeline

Only identity-relevant events.

  • Before the breach — A contractor's Uber corporate credentials are exposed (consistent with infostealer malware) and end up for sale. The attacker buys them.
  • Initial attempts — The attacker tries to log in. The account is protected by push-based MFA, so each attempt fires a prompt to the contractor's phone. The contractor declines.
  • The social-engineering step — The attacker contacts the contractor over WhatsApp, posing as Uber IT, and says approving the prompt is how to make them stop. The contractor approves one. The session is now the attacker's.
  • Internal reconnaissance — Inside, the attacker moves through internal services and network shares looking for a way up from "contractor" to "administrator."
  • The escalation — They find PowerShell scripts on a share. One contains hardcoded credentials for a domain admin account to Thycotic, Uber's PAM — the system whose entire job is to guard privileged credentials.
  • Full unlock — With the PAM open, the attacker reaches secrets for a wide range of internal systems, and is reported to have accessed Slack, internal tooling including a finance/invoicing system, cloud consoles, and Uber's HackerOne bug-bounty dashboard.
  • Sept 16, 2022 onward — Uber publishes a security update, later attributing the intrusion to an actor associated with the Lapsus$ group.

The pivot point

The story everyone tells is about the push notification, and I understand why — it's human, it's relatable, and "don't approve prompts you didn't initiate" is an easy lesson to print. But the push notification only bought the attacker a contractor's level of access. A contractor account is a bad day, not a catastrophe.

The catastrophe was already in place, written months or years earlier, and it had nothing to do with the contractor. It was a domain admin credential to the privileged access management system, sitting in cleartext, in a script, on a share that a freshly-compromised contractor account could read.

Think about what a PAM is for. It is the one box you are supposed to harden above all others — the vault that holds the keys to everything else, precisely so those keys are never lying around. The entire security model assumes that getting into the vault is hard. A plaintext domain-admin credential to that vault, parked in a script, inverts the model completely: the thing meant to be the hardest target becomes reachable from one of the softest accounts in the company. That is the pivot. The moment that credential was committed to that script, the breach's ceiling was raised from "a contractor's access" to "everything," and it would stay raised until someone found and removed it. No one did, until the attacker did.

What would have caught it

Naming controls, not products.

Phishing-resistant MFA. Push-approval MFA asks a tired human to be the security control. Number matching narrows the fatigue window; FIDO2/WebAuthn security keys close it, because there is no prompt to approve from the wrong place — the credential is bound to the legitimate origin. The single change that would have blunted the entry is moving privileged and contractor access to phishing-resistant factors.

Rate-limiting and alerting on push storms. A burst of denied MFA prompts is itself a high-fidelity signal. Capping retries and alerting on repeated denials turns "fatigue" into "detection."

Secrets scanning on shares and repositories. A domain-admin credential in a script is exactly what automated secret scanners exist to find. Running them continuously across code, shares, and build systems is how you find the plaintext key before an intruder does.

Privileged credentials that can't live in scripts. The credential to a PAM should never be a static string anyone can paste into a file. Short-lived, brokered, just-in-time credentials — issued on request and expired quickly — remove the thing that can be hardcoded at all.

Least privilege between tiers. A contractor account being able to read a share that holds admin credentials is the seam. Segmenting so that low-trust identities cannot reach high-trust secrets limits how far any single compromise travels.

The last mile

Two vendors did their jobs here, and the incident happened anyway — in the space past where their jobs ended.

The identity provider enforced MFA. That is the vendor's part, and it worked: the attacker could not simply log in. But "MFA is enabled" and "MFA that survives a determined social engineer with the user's phone number" are separated by a stretch of work — choosing phishing-resistant factors, tuning fatigue protections, training for the specific WhatsApp-pretext attack — that the product does not do for you. That stretch was unowned.

The PAM vendor shipped a vault. That worked too, as far as it went: the privileged secrets were in a managed system. But "we deployed a PAM" and "there is no plaintext credential to the PAM lying in a script" are, again, separated by a mile of operational discipline — secret scanning, credential brokering, share hygiene — that the product cannot enforce from inside its own boundary. That mile was unowned as well.

This is the pattern this series keeps finding. The vendor's responsibility stops at the edge of the product. The customer — here, busy internal teams at enormous scale — doesn't have the bandwidth to own everything past that edge. And the controls that would actually have stopped the escalation lived in between, owned by no one: phishing-resistant enrollment, secret hygiene across shares, least privilege between an outsourced contractor and the crown jewels. None of those is a feature you buy. All of them are work someone has to continuously do, across every tenant, forever.

For defenders

Five things, none of which needs a new product.

  1. Move privileged and third-party access to phishing-resistant MFA (FIDO2/WebAuthn). If you can only do one thing, do this for admins and contractors first.
  2. Alert on MFA push storms. Repeated denied prompts to one user in a short window should page someone, not just annoy the user.
  3. Run secret scanning across shares, repos, and build systems — continuously. Hardcoded credentials are found by whoever looks first. Make that you.
  4. Get static privileged credentials out of scripts. Broker them just-in-time so there is nothing durable to hardcode, and rotate anything that currently is.
  5. Segment low-trust identities away from high-trust secrets. Ask the concrete question: can a single contractor account, today, read a path that contains an admin credential? Go find out.

Built from public reporting and the parties' own disclosures; sources below.

Sources

#uber #mfa-fatigue #pam #secrets-sprawl #last-mile