Conceptual image showing cybersecurity misconfigurations enabling EvilTokens to bypass MFA in 55 organizations, compromising data protection.

EvilTokens succeeds because it never has to defeat multi-factor authentication—it targets the OAuth device authorization flow, an authentication path most Conditional Access policies never explicitly cover. This is a Phishing-as-a-Service platform Huntress tracked in partnership with Flare.io, complete with a storefront, AI-assisted lure generation, and a 24/7 support team. Device code phishing at scale is now something an attacker can subscribe to. (Source: Huntress)

The attack chain starts with the operator, not the victim. Attackers generate a legitimate Microsoft device code themselves, then embed it in a phishing lure. When your user enters that code at the real Microsoft endpoint, they authenticate against genuine infrastructure and complete MFA as normal. The resulting OAuth token—valid for up to 90 days—flows to the attacker.

Here is the Conditional Access gap that makes this work: device code flow is designed for input-constrained devices like smart TVs and printers, and most Microsoft 365 tenants have never blocked it. There is no malicious sign-in for a location-based rule to catch, no non-compliant device for a device-compliance check to flag, and no MFA challenge to fail because the victim satisfies MFA for the attacker.

  • No password compromise: The victim never hands over credentials, so credential-monitoring and password-spray detections stay quiet.
  • MFA satisfied by design: The interactive sign-in completes legitimately, so policies requiring MFA record a successful, compliant login.
  • Token replay from clean infrastructure: Attackers replay the harvested token from hosting that carries trusted IP reputation, giving risk-scoring engines no reason to raise a flag.

Infrastructure is where EvilTokens got dangerous. The campaign ran through Railway.com, a legitimate developer PaaS platform whose IP ranges are clean. Microsoft Identity Protection has no baseline reason to score a login from Railway as risky, so the attack effectively gained a cloud-hosted token harvesting engine with trusted IP reputation built in.

Just three Railway IP addresses accounted for roughly 84% of all attack traffic—a small number of deployed applications doing a large share of the damage.

The forensic footprint is subtle by design. Rather than failed logins or anomalous geolocations, the artifacts you would look for are successful device code authentications, OAuth token grants tied to input-constrained device flows, and subsequent token use from hosting providers that would not normally appear in your sign-in history. For a business, that means the compromise looks like normal activity in the audit log—an authenticated session doing authenticated things—until the attacker acts on the access.

The lures were built to pass filtering stacks, not just to fool users. Construction RFP themes dominated, which fits an industry that runs on third-party document requests. Some phishing chains ran through triple-wrapped URLs using Cisco, Trend Micro, and Microsoft's own SafeLinks in sequence, so the email arrived carrying a trusted vendor domain in the link and the filtering stack passed it.

The consequence is straightforward. A valid OAuth token lasting up to 90 days gives an attacker full access to a user's Microsoft 365 environment—mailboxes, files, and connected services—without tripping the controls organizations rely on to stop account takeover. Because the session is authenticated and MFA-compliant on paper, the access persists until the token expires or is revoked, and the attacker operates with the victim's legitimate permissions the entire time.

Why Construction Organizations Are Vulnerable and What's at Stake

The construction-themed lures in the Railway campaign weren't random. Attackers built RFP (request for proposal) themes because the construction industry runs on a constant flow of third-party document requests, and a bid invitation or contract addendum from an unfamiliar vendor is normal, expected email traffic. When your project managers open dozens of these a week, an attacker's document link doesn't stand out.

That workflow is the vulnerability. Construction relies on distributed teams: field crews, general contractors, subcontractors, architects, and suppliers all collaborating in shared cloud environments. Many of these users authenticate from job sites, personal devices, and rotating locations, which makes anomalous logins harder to spot and Conditional Access geo-rules easier for attackers to slip past.

Consider what a stolen Microsoft 365 token actually reaches inside a construction firm:

  • Project management and bid systems containing budgets, schedules, and competitive pricing that a rival or fraudster can act on directly.
  • Financial and payment data tied to draw requests, lien waivers, and vendor invoices — the exact records an attacker needs to redirect a payment.
  • Vendor and subcontractor credentials and contacts, which let an attacker send the next round of RFP lures from a domain the recipients already trust.

The token itself is the problem. In the Railway campaign, a valid OAuth token lasts up to 90 days, so an attacker holds authenticated access to your mailbox and files for months without needing to log in again. During that window they can read your email threads, learn your billing cycle, and time a fraudulent wire request to land when it looks routine.

Business email compromise is the most direct outcome. If an attacker sits inside the email chain between your project accountant and a supplier, they can insert updated banking details on a legitimate invoice. Your firm covers the redirected payment from its own funds, and the subcontractor still expects to be paid.

The operational cost runs alongside the financial one. Losing access to project management systems, or discovering that budget and bid data has been read by an outsider, can stall active jobs where crews, materials, and equipment are already scheduled. Construction runs on tight sequencing, and a delay on one trade pushes every trade behind it.

By the time the Railway investigation was published, 344 organizations had been hit across the US, Canada, Australia, New Zealand, and Germany — and construction RFP themes dominated the lures.

There's also the exposure that follows the incident. Your subcontractors and clients share sensitive project and financial data with you on the assumption you'll protect it. Once an attacker has harvested vendor contacts and sent lures onward through your account, the firms in your supply chain inherit the same attack — and they'll know where it came from.

The LSHIY campaign shows this isn't limited to firms that clicked a link. Attackers replayed validated credentials against Azure CLI at massive scale, meaning any construction organization with reused or previously exposed passwords was a target regardless of how careful its staff were with email. If your team assumed MFA closed that door, the 55 compromised accounts that had active MFA policies say otherwise. The next section covers exactly where those policies failed.

Detecting EvilTokens Activity in Azure AD and Conditional Access Logs

Start with your Azure AD sign-in logs, because both campaigns leave records there even when Conditional Access never fired. The most decision-relevant hunt today is filtering non-interactive sign-in logs for the ClientAppUsed field set to legacy or "Other clients," and for device code authentications. These are the two flows that carried the Railway and LSHIY attacks, and they show up in logs even when no MFA prompt was ever issued.

In your Azure AD sign-in logs, pivot to the non-interactive sign-in tab first. ROPC authentications against Azure CLI appear here rather than in interactive logs, which is why they slip past dashboards that only watch interactive sign-ins.

  • Filter ClientAppUsed for ROPC or legacy authentication client types, and cross-reference the application against Azure CLI and other command-line clients.
  • Filter authenticationProtocol or the authentication detail for the device code flow, then look at which application initiated it and whether the user population makes sense for input-constrained devices.
  • Check ResultType against the Conditional Access evaluation column. A successful sign-in showing "Not applied" or "Report-only" for CA is the signature of a policy that looked configured but never enforced.

The Conditional Access policy evaluation section of each sign-in record tells you why a challenge did or did not fire. If you see a token issued with CA status "Not applied," the flow fell outside your policy scope. That is exactly how 55 organizations with MFA enabled still had accounts compromised.

Their policies didn't protect them.

For token issuance anomalies, watch TokenIssuerType and correlate fresh OAuth token grants with sign-ins that carry no matching MFA event. A device code flow hands the attacker a token that can last up to 90 days, so hunt for long-lived tokens tied to sign-ins from IP ranges you don't recognize, including cloud PaaS hosting and IPv6 ranges.

Impossible travel deserves attention, but with a caveat. Attackers in the LSHIY campaign had IPs mislabeled as US-based, so a location that reads "trusted" is not proof of safety. Correlate RiskyUserState and Identity Protection risk detections against sign-in geography, and treat any successful auth from a new location that should have triggered a CA challenge but didn't as a priority alert.

What to hunt today: run the non-interactive log query for ROPC and device code flow across the last 90 days, and review every CA policy for gaps between its name and its actual scope. One organization had a policy named "Block Azure CLI" that did not block Azure CLI. Verify scope with your own eyes, not the policy title.

What to automate for tomorrow: stand up alerts that fire on any device code authentication tenant-wide, any ROPC sign-in against Azure resources, and any successful sign-in where the CA evaluation returned "Not applied" for an account that should be in scope. Alert also on report-only policies staying in report-only past their evaluation window, since two compromised organizations had MFA configured but never enforced.

In environments Capstone manages, Adlumin monitors these authentication patterns across managed environments, correlating token issuance, client app type, and Conditional Access evaluation so a device code grant or ROPC replay surfaces as an identity alert rather than a line buried in non-interactive logs. That correlation is what turns 81 million login attempts into a single actionable detection instead of noise your team has to sift through by hand.

Immediate and Long-Term Actions to Close Conditional Access Misconfigurations

The single most important action this week is to re-scope your Conditional Access policies to cover all users, all cloud apps, and all client app types. The 55 organizations that got compromised despite having MFA enabled shared the same root cause: policies scoped narrowly enough that legacy auth flows like ROPC never triggered a prompt. A policy that requires MFA only for Microsoft Admin Portals does nothing for Azure CLI traffic hitting the /token endpoint directly.

Identify

Audit every Conditional Access policy in your tenant and record its actual scope, not its name. One organization in this campaign had a policy explicitly titled "Block Azure CLI" that did not block Azure CLI. Names lie; the assignment blocks tell the truth.

  • List which policies are set to report-only versus enforced. Two compromised organizations had MFA configured but never turned on.
  • Check whether any policy relies on trusted location exclusions. Attacker IPs were mislabeled as US-based, so location-based trust waved them through.
  • Identify accounts excluded from MFA-requiring policies through group scoping, since compromised accounts in this campaign frequently sat outside the enforced groups.

Protect

Once you know where the gaps are, close the two auth flows attackers counted on. Block the OAuth device authorization flow tenant-wide, or restrict it to the specific service accounts that genuinely require it. For legacy credential replay, enforce the client-level strong authentication setting so the ROPC flow fails even when the username and password are correct.

Their policies didn't protect them because the scope never matched what the attack used.

This month, add continuous access evaluation so tokens are re-validated when risk changes rather than trusting a session for its full lifetime, and shorten sign-in token lifetimes for privileged roles. Attacker-harvested tokens can remain valid for up to 90 days, so reducing that window limits how long a stolen session stays useful. Move high-risk administrative roles to passwordless sign-in, which removes the username-and-password pair that ROPC depends on entirely.

Detect

Adlumin monitors authentication patterns across managed environments, catching the non-interactive sign-in anomalies and legacy client app activity that Conditional Access never fires on. Pair that telemetry with a report-only rollout for any new policy: run each change in learning mode for roughly 14 days first, review exactly which users would have been blocked, and carve out exceptions before enforcing. This is how you avoid the help-desk flood that stops most teams from deploying these controls in the first place.

Respond

When a compromise surfaces, revoke all active refresh tokens for the affected account rather than just resetting the password, because the stolen OAuth token survives a password change on its own. Force reauthentication, then confirm the account was not used to consent to new applications or add device-code registrations during the access window.

Recover

Long-term, treat Conditional Access as configuration that drifts. Baseline the health of every policy, then re-audit quarterly against that baseline so a scope change or a new default-excluded resource surfaces before an attacker finds it. Managing these policies as code, with version control and peer review, makes drift visible in the same way a code diff is.

Across more than 12,000 tenants reviewed during the Managed ISPM early access period, more than 50% of recommended controls were missing in 60% of tenants, including environments that already had posture tooling deployed. Having the policies is not the same as having them scoped correctly and kept current.

Validating Your Conditional Access Posture Against This Attack

Start by pulling every Conditional Access policy in your tenant and confirming that the OAuth device authorization flow is explicitly blocked. This is the one control that stops the token-harvesting technique described earlier at its source, and it's the control most environments never turned on. If you can't point to a policy that names device code flow, you're exposed.

Here's the fast validation pass a security engineer can run in an hour or two, in the order that matters.

Identify

Inventory every policy by its actual grant conditions and scope, not its display name. Export the full Conditional Access policy set and record, for each one: which users are in scope, which cloud apps are targeted, which client app types are covered, and what grant control is required.

  • Confirm at least one policy has a grant condition of authenticationFlows.transferMethods = deviceCodeFlow set to block, applied to all users except a documented exception group.
  • Check that the client app types selector includes both modern and legacy authentication clients. A policy that omits "Other clients" leaves the door open for direct token-endpoint requests.
  • Verify that userStrongAuthClientAuthNRequired is enforced so client-level authentication is required even when a username and password are valid.

Protect

Once you know the gaps, tighten scope and token handling. Narrowly scoped MFA is what let validated credentials through in the incidents above.

  • Set every MFA-requiring policy to all users, all cloud apps, all client app types. Treat named exclusions as the only exceptions, and review them.
  • Require device compliance or a phishing-resistant, passwordless method (FIDO2, certificate-based, or Windows Hello) rather than any-MFA where the device posture matters.
  • Configure token lifetimes deliberately. Long-lived refresh tokens keep a stolen session alive for extended periods, so shorten sign-in frequency for privileged roles and set refresh token maximum age within your policy rather than leaving defaults.
  • Enable Continuous Access Evaluation so revoked or risky sessions are cut off in near real time instead of surviving until the token naturally expires.

Detect

Confirm your policies actually enforce rather than watch. Filter your policy list for anything still in report-only state and promote it once you've validated the blast radius. A policy in report-only mode records what would have happened and blocks nothing.

Adlumin monitors authentication behavior across managed environments and flags sign-in patterns that match device code and direct token-endpoint activity, so a policy gap that hasn't been closed yet still surfaces as an alert.

Respond

Where validation turns up an enforced-but-ineffective policy, carve out the required exception, then move it from report-only to enforce during a controlled window. Document who is affected before you flip it so the help desk isn't caught off guard.

Red flags that mean you're vulnerable, and worth checking before you close the laptop:

  • A policy named for a control it doesn't actually apply, such as a block rule whose scope excludes the very client type it claims to stop.
  • MFA scoped to admin portals or a single user group instead of all cloud apps.
  • Location-based MFA that trusts an IP range or country, where a mislabeled address becomes a bypass.
  • Any policy left in report-only mode past its intended learning window.

The policies existed. They just didn't cover the flow the attackers used.

Run this pass against your own tenant and record the scope of each policy as it is, not as it reads. If you can't confirm device code flow and legacy auth are blocked for all users and all apps, assume they're open and fix that first.

Key Takeaway: Conditional Access Policies Are Only Effective When Enforced Completely

The most important thing to understand about both the Railway and LSHIY campaigns is that neither one defeated Conditional Access. They found the space around it. In one case, a tenant had a policy explicitly named "Block Azure CLI" that did not actually block Azure CLI. The name described the intent; the scope described the reality, and attackers only care about the scope.

This is the core lesson: a Conditional Access policy protects you only where it applies. If your MFA requirement covers admin portals but not all cloud apps, the uncovered apps are open. If it applies to some user groups but not others, the excluded accounts are open. If it runs in report-only mode, it logs what it would have done and enforces nothing.

Partial coverage is the exact condition these campaigns were built to find. Legacy auth flows like ROPC, and authorization paths like device code, stay open by default in most tenants, so attackers can count on them being reachable somewhere across thousands of organizations.

This is a configuration discipline problem, not a product limitation. The controls exist and work when their scope matches what attackers actually use.

The single most important action is to audit every Conditional Access policy by its real grant conditions and scope rather than its display name, and close the gaps before token theft happens rather than after an incident report tells you where they were. Policies drift, exceptions accumulate, and new apps appear, so treat that audit as recurring work, not a one-time fix.

TPL_TABLE_CONTENT

Top hits