Red Team Device Code Phishing Microsoft Entra Tool Release

WRAITH: A Zypher-Built Tool for Persistent Microsoft Device Code Phishing via Email Image Defacing

Tool
github.com/theemperorspath/wraith
Built by
Zypher offensive engineering
Category
Custom offensive tool for authorised red team and phishing simulations
Target
Microsoft Entra ID / Microsoft 365 accounts
WRAITH overview: dynamic device code phishing via email image defacing
WRAITH is a Zypher-built offensive tool. One spoofed email, one image URL, infinitely refreshing codes via email image defacing.

The problem device code phishing has always had

Microsoft's OAuth 2.0 device code flow is a legitimate authorisation grant for input-constrained clients: game consoles, CLI tools, IoT devices. The client asks Microsoft for a device code, the user goes to microsoft.com/devicelogin, enters the code, signs in with their normal credentials, and the client polls a token endpoint until Microsoft hands over an access token plus a refresh token. The tokens issued through this flow are indistinguishable from tokens issued through any other flow. They are scoped to Microsoft Graph, they carry the user's roles, and crucially they are already past MFA. MFA is enforced against the human at devicelogin, not against the client polling for the token. If the human completes MFA, the client wins.

This is the whole basis of device code phishing. An attacker generates a device code against a public Microsoft client ID (Office, Teams, Azure CLI), sends a target an email asking them to sign in at devicelogin with that code, and polls for the resulting token. Because the flow was designed for legitimate clients that cannot host a browser, Microsoft cannot easily block it. If the target enters the code, the attacker walks away with a token that survives MFA, session policies, and most conditional access rules that gate initial sign-in but do not re-check the token afterwards.

There has always been one problem, and it is severe enough that most engagements skip the technique. Device codes expire in fifteen minutes.

Fifteen minutes is nothing on a real corporate mailbox. The target might be in a meeting, at lunch, on the train, or three time zones behind you. They open the email an hour later. The code is dead. Every device code phishing engagement I have watched hits this wall on the first send. The operator then has to choose between two bad options:

Both options make device code phishing look amateur. Real attackers would not resend an email six times with a different code each time. The technique burns as soon as the operational tempo is wrong.

The spark of the idea, from Truffle Security

The idea for WRAITH came from a Truffle Security paper called Email Graffiti. Truffle noticed that most major email providers pull remote images at view time rather than send time. If you can control what the image URL returns, you can change what the email says, retroactively, forever after the email was sent. They used this to vandalise old emails by claiming abandoned cloud storage buckets that historic emails still referenced. A YouTube notice from 2020 could be repainted years later because the bucket that hosted its header image had been left un-owned when the account was closed. Their framing was retrospective mischief. The interesting part for offense is the mechanism: the image the target sees is decided by the server, at the moment the mail client renders the email.

The moment you internalise that, the device code expiry problem stops looking like a problem. The code does not have to live in the email. The code has to live at the URL the email references. If the code the URL serves is fresh, the email is fresh, no matter how long it has been sitting in the inbox.

What WRAITH does

WRAITH stands for Weaponised Refresh Authentication with Infinite Token Harvesting. It is a Python-based offensive tool we built at Zypher to industrialise the technique. It runs on a VPS with a phishing domain and a valid TLS certificate, and it collapses the whole attack into a single service. In a loop, it does four things:

  1. Requests a device code from Microsoft against a chosen client ID (Office is the default because it carries the broadest Graph scopes).
  2. Renders the current code onto a PNG and serves that PNG at a fixed URL such as https://verify.attacker.tld/code.png, with cache headers set so the target's mail client refetches on every open.
  3. Polls Microsoft's token endpoint every few seconds while the code is valid, waiting for the target to complete the sign-in on the real Microsoft page.
  4. When the code expires without a token, it requests a new one and repaints the image. The URL never changes. Any open of the original email fetches the new PNG and shows the fresh code.

One email carries one image URL. That URL, until the target signs in, keeps a live valid device code painted on it. Whether the target opens the email fifteen minutes after send or two days after send, the code they see is one that was minted in the last few minutes and is still valid for the next fifteen. The operator does not resend anything. The infrastructure runs by itself.

First device code rendered into the phishing email image
First open: the initial device code rendered into the email image.
Same email opened later, showing a refreshed device code
Same email, same image URL, fifteen minutes later: the code has silently refreshed.

Both screenshots above are the same email at the same URL. The only thing that changed was time.

The full attack chain

End to end, a WRAITH engagement runs like this. The operator prepares the phishing domain and TLS material. WRAITH's setup wizard handles most of it, including certbot invocation and config generation. Then the attack launches:

  1. Domain spoofing. WRAITH ships with SMTP tooling to send a single HTML email that appears to come from an internal address at the target company. Domains with DMARC set to p=none or p=quarantine are the sweet spot; strict DMARC installs get pre-flighted and the operator is warned before send.
  2. Email template. The email is deliberately boring. Options ship for a colleague asking the team to verify their account, an IT security policy update with mild urgency, and a Microsoft session-expired notification. The verification code lives inside the email as an <img> tag pointing at WRAITH's image URL. No JavaScript. No unusual attachments. Nothing a modern mail filter will flag.
  3. Landing page. The email links the target to a WRAITH-hosted /verify page that mirrors the Microsoft device login prompt and shows the current code. This is optional: the operator can also link straight to the real microsoft.com/devicelogin. Both flows land the same tokens; the fake landing page gives the operator visibility into interaction events (image loads, clicks) before the target reaches Microsoft.
  4. Server-side dynamism. WRAITH mints device codes on demand, renders them onto PNGs with the correct anti-cache headers, and rotates them automatically as they expire. Each image load is logged with IP and User-Agent so the operator can see when the target actually opened the email.
  5. Token capture. When the target completes the sign-in at devicelogin, Microsoft returns an access token and refresh token to whatever client is polling. That client is WRAITH. Tokens land in token_output.json and the polling loop shuts down.
  6. Post-compromise enumeration. WRAITH runs an automated Graph API sweep the moment a token drops: users, groups, mailboxes, Teams chats, OneDrive files, directory roles, mail rules, and OAuth apps. Everything is written to token_output_enum.json. The refresh token is stored separately so the operator can maintain access after the initial access token expires.

Why the image-defacing approach beats the alternatives

There are two obvious ways to keep a phishing artifact fresh. WRAITH uses images. Both alternatives have a shorter half-life.

Server-side redirect on a link

The naive answer is to make the phishing email contain a link to a WRAITH-hosted page, and let that page display the current code. The moment the target clicks, they hit a page with fresh content. This works, but it moves the interaction from a passive read to a deliberate click. It also puts the operator's phishing domain into the browser's URL bar, where the target and their AV will look at it more carefully. The email itself carries a URL that looks like a redirect, which is exactly what mail filters heuristically distrust.

The image approach keeps the interaction shape identical to a legitimate verification email. The code appears to be part of the message. The target never navigates to the operator's domain to see it.

Rewriting the email in place

IMAP-authenticated mail rewriting is possible but requires prior access, which is what device code phishing is supposed to be building toward. It is not a starting primitive.

Dynamic images sit in the sweet spot: no client-side interaction to trigger the refresh, no prior access required, no visible signal to the target that anything is happening between opens.

Anti-cache is load-bearing

The mechanism only works if the target's mail client fetches the image again on every open rather than serving the copy it grabbed the first time. WRAITH sets three headers on the PNG response and it is worth being explicit about them:

Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Pragma: no-cache
Expires: 0

All three matter. Cache-Control is what modern HTTP intermediaries respect. Pragma is what older HTTP/1.0 caches respect. Expires: 0 is what a subset of email clients and mid-path proxies key off. In practice, Outlook and Apple Mail obey Cache-Control; a handful of niche webmail proxies only pay attention to Expires. Setting all three is cheap insurance.

Gmail is a special case. Google proxies remote images through their own image server (ggpht.com), which caches aggressively and does not respect Cache-Control: no-store on the origin. There are two ways around this. First, most corporate mailboxes on Google Workspace can be configured to not proxy images from allow-listed senders; if the domain looks internal enough, this can be enough. Second, appending a rotating query string to the image URL in the email HTML forces Gmail to see a new URL and refetch. WRAITH supports both by convention: pick the operator posture that matches the target's mail platform.

Field results

WRAITH has been part of Zypher phishing simulations for multiple client engagements including GetConnects. The single most useful result across those engagements was not the raw token capture rate; it was the delta between what the operator could measure with static device code phishing versus with WRAITH. On the static tooling we had used previously, the median open-to-code-entry delay put more than half of opens outside the 15-minute window. On WRAITH the same distribution of open times converted almost end to end, because "outside the window" stopped being a state that existed. In the engagements where token capture was in scope, WRAITH moved device code phishing from a technique we only ran against pilot targets to one we could confidently run against the full simulation population.

Detection during those engagements was low. The email itself contains no obvious indicators: no attachments, no JavaScript, no URL that looks like a redirect, no repeated sends. The image renders as a static verification code from the target's point of view. The verification code they see is a real code, minted by Microsoft, valid for the standard 15 minutes. Even manual inspection of the email HTML shows only a normal <img> reference to an HTTPS URL on a domain that resembles Microsoft's login infrastructure.

Ethics: WRAITH is intended for authorised offensive engagements only. Every engagement it has been used in was scoped, documented, and pre-authorised by the client. The tool ships with a legal disclaimer to the same effect. It is not to be used against organisations that have not signed engagement paperwork.

Post-compromise, briefly

The tokens WRAITH returns are ordinary Graph API tokens. Everything that Microsoft Graph exposes to the compromised user is reachable. In practice the enumeration script pulls:

The refresh token is the more important artifact. Access tokens are short-lived. Refresh tokens live for up to 90 days by default and can be silently exchanged for new access tokens without any further user interaction. WRAITH has a --refresh mode that takes a saved token file and mints a fresh access token from the stored refresh token, so the operator can pick up an engagement days later without the target ever knowing.

Detection and defense

The defensive side is where this writeup is genuinely useful, because most of the recommendations are cheap and none of them are exotic.

Refresh tokens deserve their own note. Even after the initial compromise, tenant-level revocation of refresh tokens is available and instant. If an operator's activity is caught (or if the user reports the phishing email after the fact), revoking refresh tokens for the user closes the persistence window entirely.

What WRAITH is not

WRAITH is not a novel vulnerability. Device code phishing is a well-known technique. The image-defacing trick is not new either; Truffle's Email Graffiti described the mechanism at large, and dynamic email images predate that paper. What WRAITH is, is the Zypher-built composition of the two. Nobody had assembled them into a persistent device code phishing rig you can operate as a single tool. That composition is what makes the technique viable on real engagements at real target volumes, and it is why we shipped it as open-source tooling rather than keeping it internal.

It is also not a general-purpose OAuth phishing framework. It targets Microsoft's device code flow specifically. Google, Okta, and other IdPs have their own equivalents; the same email image-defacing trick would in principle apply, but the concrete implementation lives in WRAITH's Microsoft-shaped repo for now.

References