Skip to content
Block-Continue
Go back

The Account That Cried VPN

Most of the alerts I work start with something suspicious coming in. This one started with something suspicious going out, which is usually worse news. An account that’s sending phishing is already compromised, so what’s left to figure out is how long, and what else the attacker touched on the way through.

The short version: a user’s account got phished, the attacker talked their way past MFA over text message, pointed the user’s refund payments at a new bank account, and set up inbox rules so nobody would notice. The long version is more interesting, mostly because the user’s own habits made the logs look far worse than the actual intrusion.

The Alert

Our outbound mail filtering includes a rule set built from strings we’ve pulled out of past phishing campaigns. When a template shows up once, the distinctive bits of it (subject phrasing, lure text, the formatting quirks attackers reuse across campaigns) go into the filter so the next copy gets caught on the way out instead of landing in somebody else’s inbox.

That rule fired on a batch of messages leaving one of our own accounts. The lure was a fake MFA enrollment notice, the kind that tells the recipient their authentication settings need updating and links to a login page that has never been anywhere near our identity provider. Every one of the messages was quarantined before delivery, which is the good part of the story. The rest is about how the account got there.

The Noise Problem

First step on any compromised account is pulling sign-in history and looking for where it goes weird. This account went weird in April.

Commercial VPN ranges, CDN proxy addresses, onion routing exit nodes, and a steady rotation of locations that no single human commutes between. Taken at face value, it read like an attacker with five months of quiet persistence, and I spent a while working from that assumption, trying to pin down the day the threat actor first showed up.

The assumption was wrong. Talking to the user cleared it up fast: they had been using a privacy-focused browser with onion routing, plus a commercial VPN, for research work done with the knowledge and guidance of the person supervising it. Every one of those strange sign-ins was them.

This is the part of the investigation I keep coming back to. The logs weren’t wrong. Every IP was accurately recorded, and every sign-in really did come from an anonymizing service. What the logs couldn’t tell me was intent, and the baseline I’d normally compare against didn’t exist for this account. When a user’s normal behavior looks exactly like attacker infrastructure, the anomaly you’re hunting for doesn’t stand out; it sits inside months of noise that looks equally bad. A conversation did in twenty minutes what hours of log filtering couldn’t, and once I knew which traffic was theirs, the malicious sessions got a lot easier to see.

How They Got In

Some context first. Earlier in the year, the user had replaced the device registered for MFA by going through the helpdesk, verifying their identity, and getting a temporary bypass code to enroll the new phone. That request was legitimate and came from the user. It matters here mostly because it defined which factors were registered to the account when the attacker showed up.

The attack itself ran in two stages.

First, the credential. The user most likely entered their username and password on a spoofed login page hosted on a third-party website builder platform. Free site builders are popular for this because the pages are quick to spin up, the domains carry a legitimate reputation, and a takedown just means building another one.

Second, the MFA. The attacker tried the password and triggered a push notification to the user’s phone. The user rejected it. Good instinct, and exactly what we train people to do. The attacker pivoted immediately, texting the user while posing as institutional support and talking them into replying with the SMS verification code that had just landed on their phone. That code was the one that worked.

If you read the push fatigue post from March, this is its smarter cousin. That attacker spammed prompts until someone tapped approve out of annoyance. This one took the rejection as a signal to change channels and add a human voice. The push factor held. The fallback didn’t.

What They Did With It

With a completed MFA challenge, the attacker established valid sessions and application tokens from external IP ranges. Location and ISP were what gave these away: they came from places and providers that didn’t match the user’s own VPN and onion routing pattern, and in the interview the user confirmed they hadn’t traveled during that window. From there, the objective was money, not email.

They went into the self-service financial portal and changed the direct deposit and refund enrollment settings, which would route the user’s next disbursement to an account the attacker controlled. That’s the whole business model: the account is only worth something to them until the next refund lands.

Then they covered it. The attacker created inbox rules that caught automated notifications from the payment and administrative systems and silently deleted them. Every “your direct deposit information has changed” email, the exact message designed to tell the user something was wrong, went straight to nowhere. Only after the payment changes and the rules were in place did the account start sending the phishing batch that tripped our filter.

This is the part of the playbook worth memorizing. A change to payment settings plus a rule that eats the confirmation email is the whole attack in two moves, and the second move is the one you can reliably detect.

StageTechniqueWhere it could have been caught
Initial accessCredential phishing on a site-builder pageLink filtering, user reporting
MFAPush rejected, then code phished over SMSPhishing-resistant MFA, weak fallback factors removed
PersistenceValid sessions and app tokensSession risk scoring, token revocation on anomaly
MonetizationDirect deposit and refund changesOut-of-band confirmation of payment changes
Defense evasionInbox rules deleting payment noticesAlerting on rule creation targeting financial senders
DiscoveryOutbound phishing from the accountOutbound string filter (this is what fired)

The Complications

Clean compromises are rare, and this one came with three extra problems that made containment messier than a password reset and a session kill.

The user had exported a substantial amount of data from their institutional account to a personal productivity tenant we don’t manage. They confirmed they’d been moving their data from one cloud productivity suite to another, and as best we can tell the migration started before the compromise, so this looks like the user’s doing, not the attacker’s. Either way, that data now lives somewhere outside our visibility, and we can’t confirm how it’s protected or who else can reach it.

They had also signed up for an unverified third-party service that automatically rotates credentials across their external accounts. I understand the appeal. Handing a service you’ve never vetted the keys to every account you own is a strange way to reduce risk, though, and it meant we couldn’t be confident which of the user’s other credentials were still theirs alone.

Finally, the user reported seeing their cursor move on their desktop without their input. That’s the kind of detail that turns an account incident into a possible endpoint incident, since it suggests remote access software may be installed on the machine. It’s a personal device, so we had no way to run forensics on it, and the question stays open. A wandering cursor has plenty of boring explanations too, which is exactly why we couldn’t rule anything out in either direction.

None of these three is proof of anything on its own. Together they meant the account reset couldn’t be the end of the story.

Containment

We reset the password, revoked all active sessions and refresh tokens, removed the malicious inbox rules, and cleared and re-registered the MFA methods on the account. The financial office reverted the direct deposit and refund changes and flagged the account for manual review on any further payment changes. The endpoint was the harder call. Since it’s a personal device, we recommended the user have it examined by a vendor who does that work, and we blocked the machine from the institutional network by MAC address until they can show it’s clean. The user also got a direct conversation about the data export and the credential rotation service.

What I’d Change

The obvious lesson is MFA, and it’s worth being precise about it, because this incident shows why “we have MFA” is an incomplete sentence. The push notification did its job. The user rejected it. The attacker won anyway by walking around it to a weaker factor on the same account. An account’s MFA is only as strong as the weakest method registered to it, and codes are about as weak as it gets, since anything a user can read off a screen, a user can read to an attacker.

Number matching gets pitched as the fix for this, and it only partly is. It kills push fatigue, because a user can’t approve a prompt blind. It does nothing when the user is being coached in real time, and it does nothing against an adversary-in-the-middle proxy, where the user completes a real MFA challenge on a fake page and the attacker lifts the resulting session. FIDO2 and WebAuthn hardware keys or passkeys are the real answer, because the credential is bound to the legitimate site’s origin. There’s no code to read aloud and nothing for a fake page to relay. Getting there across a large user population is a slog, but for accounts that can touch payment settings, it’s worth putting at the front of the line.

The cheaper and faster lesson is detection on inbox rules. Rules that delete or redirect mail from payment, payroll, or billing senders are rare in legitimate use and nearly universal in financial diversion attacks. Real-time alerting on rule creation targeting those senders would have surfaced this well before the outbound phishing did.

The last lesson doesn’t fit neatly into a policy. The logs on this account were accurate and useless until I talked to the person behind them. Users running VPNs and privacy browsers for legitimate reasons are going to become more common, not less, and every one of them erases the baseline that anomaly detection depends on. Sometimes the fastest path through five months of noise is a phone call.

For most of this year, the user looked more like an attacker than the attacker did. I don’t expect this to be the last account where that’s true.


This writeup describes an incident at a large institution. All internal IPs have been substituted with RFC 1918 ranges and identifying details have been generalized.


Share this post on:

Previous Post
Rogue or Weaponized? Untangling This Summer's AI Hacking Headlines
Next Post
The Party Invite That Wasn't: Anatomy of a ScreenConnect Phishing Campaign