Vectra lifted bearer tokens off Teams disk
Why Microsoft Teams session tokens leak between workspace and consumer accounts, how bearer-token replay bypasses MFA, and what fires - and doesn't - in telemetry.
Microsoft Teams writes session material to local disk. The desktop client is Electron. It persists authentication state in a Cookies SQLite database and a leveldb key-value store under the user profile - %AppData%\Microsoft\Teams on the classic client, the WebView2 EBWebView cache on the new client. Vectra disclosed in 2022 that the classic client wrote OAuth access tokens and Skype tokens in cleartext to those files. Microsoft declined an immediate fix. The material sat readable by any process running in the user’s context. Map it to CWE-312, cleartext storage of sensitive information, and CWE-522, insufficiently protected credentials.
That is the storage surface. The architecture is the actual problem.
The token model is bearer. An Entra ID access token is a signed JWT. The resource server - SharePoint, Exchange Online, the Teams service - validates the signature, the aud audience claim, the tid tenant claim, the oid object identifier, and the expiry. It does not validate who is presenting the token. Possession is authorization. A JWT lifted off disk authenticates the same as a JWT held by the client that earned it. There is no cryptographic binding to the device that performed the interactive sign-in. That is the design, not a defect, and it is why the cache is worth stealing.
As a local-storage weakness the cleartext token scores low. The CVSS v3 vector carries AV:L, local attack vector, which keeps the base under 7 and reads as medium on a triage queue. The number understates the exposure. The object it exposes is a network-authenticating credential with a live session behind it. Score the storage bug in isolation and it looks like a workstation-hygiene issue. Score what the token does when replayed against graph.microsoft.com from an attacker host and the picture changes.
Now the multi-account condition. The current Teams client holds work-or-school accounts and consumer accounts in one process, under one OS user. A work account issues from an Entra tenant. A consumer account issues from the Microsoft account authority, tenant 9188040d-6c67-4c5b-b112-36a304b66dae. The client partitions these into separate profile stores on disk, but the boundary between a workspace instance and a personal instance is a claim inside a token, not a device-bound key. A harvested access token carries its own iss and tid. Replay is per-token and per-audience. The isolation that looks like account separation in the UI is claim-scoped, not enforced by any secret the endpoint has to prove it still holds.
The exploit path has two shapes. At rest, an infostealer running as the user reads the Cookies database and the leveldb cache and exfiltrates the tokens. This is commodity work - RedLine, Raccoon, Lumma, and StealC all target browser and Electron token stores. Map it to T1528, steal application access token, and T1539, steal web session cookie, with T1555.003 where the collector walks credential stores. No exploit primitive is required. No use-after-free, no heap corruption, no privilege escalation. The token is a file the user can already read, and the malware inherits the user’s context. In transit, an adversary-in-the-middle proxy sits between victim and identity provider. Evilginx and its forks relay the real login page, capture the session cookie and the issued token after the victim completes MFA, and hand the operator a working session. Map it to T1557 for the relay and T1550.004, use of a web session cookie as alternate authentication material.
Either path ends in the same place. The attacker holds a valid token and replays it. MITRE T1550.001, application access token, covers the replay against the resource. The token satisfies authentication on its own. No password is transmitted. No second factor is prompted, because the token already encodes that the factor was met at issuance. The refresh token, if captured, extends the position - Entra refresh tokens default to a 90-day sliding window, and the access tokens minted from them run 60 to 90 minutes each with randomized lifetimes. Steal the refresh token and the session regenerates itself until the window closes or something revokes it.
This is not theoretical and it is not fringe. Storm-0558 forged tokens against Exchange Online in 2023 after obtaining a Microsoft consumer signing key and abusing a validation flaw that let a consumer-scope key mint enterprise-scope tokens - token forgery, T1606, forge web credentials, executed at the trust-root level rather than the cache level, but the same portable-bearer model is what made the forged token usable. Midnight Blizzard ran token and session theft against Microsoft corporate accounts. Scattered Spider replays session tokens harvested through AiTM phishing to skip MFA entirely on downstream SaaS. Okta session hijacking in 2023 followed the identical pattern - steal the session artifact, replay it, inherit the authenticated state. The common factor across all of them is not a specific bug in a specific build. It is that a session artifact is a portable credential and the endpoint cannot tell a legitimate holder from a thief.
Telemetry is where defenders find out how thin the signal is. A replayed token produces a sign-in event in the Entra ID sign-in logs, but it lands in AADNonInteractiveUserSignInLogs, not the interactive stream. There is no MFA challenge to log because no interactive authentication occurred. authenticationRequirement reads as already satisfied. Entra ID Protection can raise anomalous-token or unfamiliar-sign-in-properties risk, and impossible-travel detection can fire when the replay originates far from the victim. Both are evadable. Operators route replay through residential proxies in the victim’s geography, and the token’s own claims - oid, ipaddr when present, device claims - either match or are absent, so the heuristic has little to grip. Microsoft Defender for Cloud Apps and Defender XDR surface some token-theft patterns through correlation, but a single replay from a plausible location inside the token lifetime is low-signal by construction.
Local harvesting is quieter still. Reading the Cookies database and the leveldb cache is a file read against a store the user already owns. Sysmon Event ID 11 records the client creating those files, not a third process reading them. Catching the read requires a SACL on the cache path emitting Windows Security Event 4663 for object access, or an EDR file-sensor tuned to flag non-Teams processes touching the Teams profile. Most deployments run neither. Sysmon Event ID 10, process access, matters for LSASS-class theft and does not apply here, because nothing needs to open a protected process to read a cleartext file. The at-rest theft leaves almost nothing unless the store is instrumented in advance.
Microsoft has moved the boundary since the 2022 disclosure. The new client uses WebView2 and app-bound encryption on the cache, and DPAPI protects the token store so a raw file copy no longer yields plaintext to an offline reader. The Primary Refresh Token is bound to the device through the TPM, so the PRT itself does not travel. access not constrained at runtime Evaluation shortens the revocation gap for supported resources by pushing near-real-time invalidation instead of waiting for token expiry. Conditional Access token protection, still in preview for a subset of flows, cryptographically binds sign-in session tokens to the device and breaks the replay-from-elsewhere pattern for the sessions it covers.
The residual exposure survives all of it. App-bound encryption and DPAPI defeat the offline file copy, but an infostealer running as the user runs inside the context that can decrypt - the protection stops a copied file, not code executing as the account. The PRT is device-bound, yet the access tokens and refresh tokens minted from a live session are still bearer artifacts, and an AiTM proxy captures those after the device-bound step completes. CAE narrows the window but does not close it, and it only applies to resources that honor it. Token protection is not yet general, so the majority of session tokens in a real tenant remain replayable within their lifetime by whoever holds the bytes.
The patch boundary is DPAPI-encrypted at-rest storage plus device-bound PRT plus CAE plus token protection where available. Past that boundary the model is unchanged. A session token proves that an authentication happened once, somewhere. It does not prove the presenter is the party that authenticated. Until the presented artifact is bound to a key the endpoint verifies on every request, the workspace instance and the consumer instance are separated by a claim, and a claim is data. Data leaks. Anyone escalating an active session-theft case should route it to their identity and incident response teams for token revocation and session invalidation, because rotating a password does nothing to a token that was already issued.
Keep Reading
oauthOne bearer token, replayed from a residential proxy
How attackers abuse OAuth 2.0 at scale via consent phishing, device code flow, and service principal credentials - and why endpoint EDR sees none of it.
session-fixationVictim types the password, attacker keeps the token
CVE-2023-4714 session fixation (CWE-384) explained: how attackers plant a session ID, bypass MFA, what fires in telemetry, and why rotation alone is not enough.
vulnerability-researchNetScaler trusts snprintf, leaks adjacent heap memory
Why 'silent' vulnerabilities like Citrix Bleed (CVE-2023-4966) are already exploited at the network edge, what they produce in telemetry, and where defenders are blind.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.