EvilTokens Client-Side Phishing Interaction
An intruder has delivered an AI-tailored phishing lure that, when opened, initiates high-frequency background polling to a malicious Node.js endpoint while redirecting the user to the Microsoft device login portal.
Based on research by Microsoft 2026-09-23 9 steps · 3 queries T1041 T1115 T1566.001 T1566.002
Brief
Why Now
The Microsoft Security Blog recently detailed the mechanics of EvilTokens in the article Unmasking EvilTokens: Getting to the root of device code phishing (https://www.microsoft.com/en-us/security/blog/2026/09/22/unmasking-eviltokens-getting-to-the-root-of-device-code-phishing/). This PhaaS platform has already compromised thousands of organizations by automating the capture of session tokens through device code phishing. Because the platform uses AI to tailor lures and frequently rotates its infrastructure, traditional indicator-based detections often fail to catch the initial interaction. This hunt addresses that gap by focusing on the persistent behavioral patterns inherent to the platform's operation.
How the Hunt Flows
The first phase targets the initial delivery mechanism. A query searches the hb_file_activity surface for the creation of new files in user-writable directories such as Downloads or the Desktop. It specifically looks for HTML, HTM, or PDF files that use lure themes identified in the research, including invoices, RFPs, proposals, or compensation benefits. While the presence of these files is not definitive proof of an attack, they provide the necessary context for the subsequent behavioral checks. The second phase pivots to network behavior to identify the characteristic 3-5 second polling interval used by the EvilTokens background script. The hunt queries the hb_http_activity surface for requests containing a /state path sent to non-trusted domains. By grouping these requests by host and calculating the average time between pings, the hunt identifies the automated polling behavior of the checkStatus function. A consistent gap of less than six seconds over multiple requests strongly suggests a phishing kit loop rather than manual user activity. The third phase checks for concurrent navigation to the legitimate Microsoft device login portal. The EvilTokens script redirects users to this portal to complete the device code entry. The hunt looks for hits on the /devicelogin path on microsoft.com that occur in the same timeframe as the suspicious background polling. This correlation is the key to identifying a high-confidence phishing interaction. Finally, an analyst or automated agent triages these combined signals. The agent reviews the file touches, the frequency of the background pings, and the portal redirection to assign a verdict of malicious, suspicious, or benign for each host. This multi-surface approach ensures that the hunt finds active phishing sessions even when the specific URLs or file hashes are previously unseen.
What This Hunt Cannot See
This hunt has two primary blind spots. First, it requires visibility into the HTTP path to identify the /state polling endpoint. If the environment only provides SNI-level visibility into encrypted traffic, the timing pattern remains visible but the specific endpoint path does not. Second, the hunt may miss lures that never touch the disk. If a user interacts with a phishing link directly within their browser without downloading a file, the file activity surface will not record any lure creation.
Steps
-
Suspicious Lure File Creation
Query · scopingIdentify the creation of potential phishing lures in user-writable paths with themes mentioned in the EvilTokens research.
reads hb_file_activitysqlSELECT device_hostname, actor_user_name, file_path, file_name, time FROM hb_file_activity WHERE activity_id = 1 AND (LOWER(file_path) LIKE '%\\downloads\\%' OR LOWER(file_path) LIKE '%\\desktop\\%') AND (instr(',' || '{{lure_extensions}}' || ',', ',' || LOWER(SUBSTR(file_name, -4)) || ',') > 0 OR instr(',' || '{{lure_extensions}}' || ',', ',' || LOWER(SUBSTR(file_name, -5)) || ',') > 0) AND (LOWER(file_name) LIKE '%invoice%' OR LOWER(file_name) LIKE '%rfp%' OR LOWER(file_name) LIKE '%proposal%' OR LOWER(file_name) LIKE '%expiration%' OR LOWER(file_name) LIKE '%compensation%' OR LOWER(file_name) LIKE '%benefits%') AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. Rows mapping lures to hosts. Silence is not evidence of absence as lures can be hosted purely in-browser without a file download.
-
High-Frequency Background Polling
Query · detection candidateIdentify the EvilTokens checkStatus polling behavior, which pings a /state endpoint every 3-5 seconds.
reads hb_http_activitysqlSELECT device_hostname, src_endpoint_ip, url_hostname, COUNT(*) as request_count, MIN(time) as first_ping, MAX(time) as last_ping FROM hb_http_activity WHERE url_path LIKE '%/state%' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND instr(',' || '{{trusted_domains}}' || ',', ',' || LOWER(url_hostname) || ',') = 0 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY device_hostname, src_endpoint_ip, url_hostname HAVING request_count > 10 AND (strftime('%s', MAX(time)) - strftime('%s', MIN(time))) / request_count <= 6What a hit looks like. A single host making frequent requests to a non-Microsoft domain with a consistent sub-6-second gap. This indicates the phishing kit loop.
-
Triage Phishing Interaction
Agent triageCorrelate lure files, background polling, and device login redirection per host to determine if a successful phishing interaction occurred.
-
Route on Interaction Verdict
DecisionDirect confirmed beachhead hosts to containment.
-
Isolate Affected Host
Response actionIsolate the host to prevent further interaction with the PhaaS infrastructure or token capture.
-
Manual Analyst Review
Analyst taskConfirm the findings and identify the specific polling domain for blocklisting.
-
Hunt Close-Out
Analyst taskFinalize documentation and consider promoting the polling logic to a standing detection rule.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| AI-Tailored Phishing Delivery T1566 |
Yes | suspicious-lure-files |
| Client-Side Device Code Interaction T1115 |
Yes | background-state-polling, device-login-navigation |
| MFA Bypass via Device Code Flow | Out of scope | Belongs to another part of the 'Unmasking EvilTokens: Getting to the root of device code phishing' series. |
| Concealed Communication via Inbox Rules | Out of scope | Belongs to another part of the 'Unmasking EvilTokens: Getting to the root of device code phishing' series. |
| Long-Term Persistence via Device Registration | Out of scope | Belongs to another part of the 'Unmasking EvilTokens: Getting to the root of device code phishing' series. |
| Graph API Recon and Email Exfiltration T1041 |
Out of scope | Belongs to another part of the 'Unmasking EvilTokens: Getting to the root of device code phishing' series. |
Blind spots
- Needs TLS interception or EDR browser-extension visibility. If the network source only logs the hostname (via SNI) and not the path, the polling pattern cannot be confirmed, potentially hiding the automated nature of the phishing page. It would answer Can we see the URL path /state within encrypted HTTPS traffic?.
- Needs hb_http_activity. EvilTokens can function purely via a link in a browser; if the user never downloads the PDF or HTML lure, the hb_file_activity step will return no rows. It would answer Does the hunt miss lures that never touch the disk?.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
lookback_days | number | 14 | Days of history to examine. |
lure_extensions | list[string] | .html, .htm, .pdf | Common lure file extensions associated with EvilTokens campaigns. |
scope_hosts | list[host] | — | Specific hostnames to scope the hunt; leave empty for fleet-wide. |
trusted_domains | list[domain] | microsoft.com, google.com, bing.com, apple.com, linkedin.com | Known-good domains to exclude from background polling results. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
| Web server / proxy logs | siem | network |
Source
---
analysis: A standard rule might flag a known malicious URL; this hunt uses behavioral
timing (the 3-5 second ping interval) and cross-surface correlation (file touch
vs. network redirect) to find zero-day phishing infrastructure.
blind_spots:
- id: encrypted-http-visibility
question: Can we see the URL path /state within encrypted HTTPS traffic?
requires: TLS interception or EDR browser-extension visibility
risk: If the network source only logs the hostname (via SNI) and not the path, the
polling pattern cannot be confirmed, potentially hiding the automated nature of
the phishing page.
stage: device-code-polling-interaction
- id: memory-only-lures
question: Does the hunt miss lures that never touch the disk?
requires: hb_http_activity
risk: EvilTokens can function purely via a link in a browser; if the user never
downloads the PDF or HTML lure, the hb_file_activity step will return no rows.
stage: initial-access-ai-phishing
coverage:
- stage: initial-access-ai-phishing
status: covered
steps:
- suspicious-lure-files
- stage: device-code-polling-interaction
status: covered
steps:
- background-state-polling
- device-login-navigation
- reason: 'Belongs to another part of the ''Unmasking EvilTokens: Getting to the root
of device code phishing'' series.'
stage: unauthorized-device-code-authentication
status: out_of_scope
- reason: 'Belongs to another part of the ''Unmasking EvilTokens: Getting to the root
of device code phishing'' series.'
stage: malicious-inbox-rule-persistence
status: out_of_scope
- reason: 'Belongs to another part of the ''Unmasking EvilTokens: Getting to the root
of device code phishing'' series.'
stage: rogue-device-registration
status: out_of_scope
- reason: 'Belongs to another part of the ''Unmasking EvilTokens: Getting to the root
of device code phishing'' series.'
stage: graph-api-reconnaissance-and-collection
status: out_of_scope
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: promote-to-detection
justification: EvilTokens has compromised over 10,000 organizations by automating
the capture of session tokens. Identifying the interaction phase prevents unauthorized
account access.
methodology: model-assisted
trigger: intel-report
hypothesis: An intruder has delivered an AI-tailored phishing lure that, when opened,
initiates high-frequency background polling to a malicious Node.js endpoint while
redirecting the user to the Microsoft device login portal.
labels:
- hunt
- attack.t1566.001
- attack.t1566.002
- attack.t1115
- attack.t1041
name: EvilTokens Client-Side Phishing Interaction
parameters:
lookback_days:
default: '14'
description: Days of history to examine.
type: number
lure_extensions:
default:
- .html
- .htm
- .pdf
description: Common lure file extensions associated with EvilTokens campaigns.
from:
kind: article
observed: '2026-09-22'
ref: msrc-blog-eviltokens
type: list[string]
scope_hosts:
default: []
description: Specific hostnames to scope the hunt; leave empty for fleet-wide.
type: list[host]
trusted_domains:
default:
- microsoft.com
- google.com
- bing.com
- apple.com
- linkedin.com
description: Known-good domains to exclude from background polling results.
from:
kind: article
observed: '2026-09-22'
ref: https://www.microsoft.com/en-us/security/blog/2026/09/22/unmasking-eviltokens-getting-to-the-root-of-device-code-phishing/
type: list[domain]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.microsoft.com/en-us/security/blog/2026/09/22/unmasking-eviltokens-getting-to-the-root-of-device-code-phishing/
gates:
- dry-run
- lint
model: hb_google/gemini-3-flash-preview
rationale: Focus on endpoints with active users in the last 14 days. Prioritize users
with roles often targeted by BEC lures, such as finance or HR.
references:
- name: 'MSRC - Unmasking EvilTokens: Getting to the root of device code phishing'
url: https://www.microsoft.com/en-us/security/blog/2026/09/22/unmasking-eviltokens-getting-to-the-root-of-device-code-phishing/
related:
- hunt: unauthorized-device-code-authentication
reason: This hunt detects the interaction; a follow-on hunt is needed to identify
successful, unauthorized sign-ins in the authentication logs.
relation: follows
scenario:
stages:
- name: AI-Tailored Phishing Delivery
observables:
- PDF attachments
- HTML file attachments
- Invoices or RFP themes
- 'High-pressure lures like ''Action Required: Password Expiration'''
- Short-lived polling node URLs
slug: initial-access-ai-phishing
tactic: initial-access
techniques:
- T1566
- name: Client-Side Device Code Interaction
observables:
- checkStatus() function
- setInterval polling every 3-5 seconds
- Pings to /state endpoint
- Automated clipboard copy of device codes
- Redirects to microsoft.com/devicelogin
- Buttons labeled 'Continue with Microsoft' or 'Copy Code'
slug: device-code-polling-interaction
tactic: execution
techniques:
- T1115
- name: MFA Bypass via Device Code Flow
observables:
- Sign-in to 'Device Registration Service' resource
- OAuth device code flow authentications from anomalous IPs
- Authentication sessions decoupled from the originating lure session
slug: unauthorized-device-code-authentication
tactic: credential-access
- name: Concealed Communication via Inbox Rules
observables:
- Creation of new inbox rules designed to hide communications
- Rules filtering for specific keywords or alerting to Telegram
slug: malicious-inbox-rule-persistence
tactic: persistence
- name: Long-Term Persistence via Device Registration
observables:
- Registration of new devices within 10 minutes of breach
- Generation of Primary Refresh Tokens (PRT) for unauthorized devices
slug: rogue-device-registration
tactic: persistence
- name: Graph API Recon and Email Exfiltration
observables:
- Microsoft Graph API calls for organizational structure mapping
- Bulk email exfiltration using stolen tokens
- AI-assisted analysis of compromised inbox content
slug: graph-api-reconnaissance-and-collection
tactic: collection
techniques:
- T1041
summary: EvilTokens is a PhaaS platform operated by Storm-2992 that facilitates
device code phishing via AI-tailored lures and automated polling infrastructure.
The attack steals OAuth tokens to bypass MFA, enabling threat actors to maintain
persistence through malicious inbox rules, register new devices, and exfiltrate
email data via the Microsoft Graph API.
series:
index: 1
slug: unmasking-eviltokens-getting-to-the-root-of-device-code-phishing
title: 'Unmasking EvilTokens: Getting to the root of device code phishing'
total: 2
severity: high
targets:
analyst:
name: Tier-2 analyst
role: analyst
endpoint:
category: endpoint
name: Endpoint telemetry (hb_ surfaces)
telemetry:
- endpoint
hunter:
agent: true
name: Hunt agent
web:
category: siem
name: Web server / proxy logs
telemetry:
- network
tlp: clear
type: investigation
---
# EvilTokens Client-Side Phishing Interaction
EvilTokens is a PhaaS platform that specializes in device code phishing. This hunt identifies the early stages of an attack by detecting the characteristic 3-5 second polling interval of the background script ('checkStatus') and the concurrent user redirection to microsoft.com/devicelogin. By correlating these behaviors with the presence of suspicious invoice or RFP-themed lure files, we can identify sessions that are actively being phished before token theft is completed. This behavioral approach bypasses rotating domains and AI-generated lures that evade standard signature-based controls.
## suspicious-lure-files
<!-- Suspicious Lure File Creation -->
Identify the creation of potential phishing lures in user-writable paths with themes mentioned in the EvilTokens research.
```sqlite target=endpoint role=scoping params=(lookback_days=lookback_days, lure_extensions=lure_extensions)
~~~yaml
expected: Rows mapping lures to hosts. Silence is not evidence of absence as lures
can be hosted purely in-browser without a file download.
reads:
- device_hostname
- actor_user_name
- file_path
- file_name
- time
silence: not_evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-23'
~~~
SELECT device_hostname, actor_user_name, file_path, file_name, time FROM hb_file_activity WHERE activity_id = 1 AND (LOWER(file_path) LIKE '%\\downloads\\%' OR LOWER(file_path) LIKE '%\\desktop\\%') AND (instr(',' || '{{lure_extensions}}' || ',', ',' || LOWER(SUBSTR(file_name, -4)) || ',') > 0 OR instr(',' || '{{lure_extensions}}' || ',', ',' || LOWER(SUBSTR(file_name, -5)) || ',') > 0) AND (LOWER(file_name) LIKE '%invoice%' OR LOWER(file_name) LIKE '%rfp%' OR LOWER(file_name) LIKE '%proposal%' OR LOWER(file_name) LIKE '%expiration%' OR LOWER(file_name) LIKE '%compensation%' OR LOWER(file_name) LIKE '%benefits%') AND time >= datetime('now', '-{{lookback_days}} days')
```
## parallel-interaction
<!-- Examine Polling and Redirection -->
parallel:
- → background-state-polling
- → device-login-navigation
join: → triage-interaction
## background-state-polling
<!-- High-Frequency Background Polling -->
Identify the EvilTokens checkStatus polling behavior, which pings a /state endpoint every 3-5 seconds.
```sqlite target=web role=detection-candidate params=(lookback_days=lookback_days, scope_hosts=scope_hosts, trusted_domains=trusted_domains)
~~~yaml
expected: A single host making frequent requests to a non-Microsoft domain with a
consistent sub-6-second gap. This indicates the phishing kit loop.
prevalence:
by: device_hostname
key:
- url_hostname
rare_below: 3
reads:
- device_hostname
- src_endpoint_ip
- url_hostname
- url_path
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-09-23'
~~~
SELECT device_hostname, src_endpoint_ip, url_hostname, COUNT(*) as request_count, MIN(time) as first_ping, MAX(time) as last_ping FROM hb_http_activity WHERE url_path LIKE '%/state%' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND instr(',' || '{{trusted_domains}}' || ',', ',' || LOWER(url_hostname) || ',') = 0 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY device_hostname, src_endpoint_ip, url_hostname HAVING request_count > 10 AND (strftime('%s', MAX(time)) - strftime('%s', MIN(time))) / request_count <= 6
```
## device-login-navigation
<!-- Navigation to Microsoft Device Login -->
Confirm the user navigated to the legitimate device login portal, which is the destination for the EvilTokens redirect.
```sqlite target=web role=enrichment params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: A visit to the device login page. While legitimate, its timing must be weighed
against lure delivery and polling behavior.
reads:
- device_hostname
- actor_user_name
- url_hostname
- url_path
- referrer
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-09-23'
~~~
SELECT device_hostname, actor_user_name, url_hostname, url_path, referrer, time FROM hb_http_activity WHERE url_hostname = 'microsoft.com' AND url_path LIKE '/devicelogin%' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## triage-interaction
<!-- Triage Phishing Interaction -->
```agent target=hunter
cite: required
context:
- suspicious-lure-files
- background-state-polling
- device-login-navigation
max_iterations: 4
objective: Determine if any single host shows a combination of suspicious lure creation,
consistent sub-6-second background polling to a non-trusted domain, and a visit
to microsoft.com/devicelogin.
success_criteria: A per-host verdict of malicious, suspicious, or benign with cited
rows for any host showing concurrent polling and redirection.
tools:
- endpoint
- web
```
## route-on-verdict
<!-- Route on Interaction Verdict -->
if~: "the triage-interaction verdict is malicious for at least one host" (confidence: high, judge=hunter)
then: → isolate-host
indeterminate: → manual-analyst-review
unavailable: → manual-analyst-review (blind_spot: encrypted-http-visibility)
else: → manual-analyst-review
## isolate-host
<!-- Isolate Affected Host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the endpoint. Revoke all active sessions for the identified user in Azure AD/M365 and force a password reset immediately.
```
→ manual-analyst-review
## manual-analyst-review
<!-- Manual Analyst Review -->
```manual target=analyst
Review the url_hostname identified in the polling step. Use whois or threat intelligence to confirm Node.js infrastructure. Check for other hosts visiting the same domain.
```
→ close-out
## close-out
<!-- Hunt Close-Out -->
```manual target=analyst
Record the results. If the polling query produced high-fidelity results without false positives, promote it to a standing detection candidate.
```
→ end
Run it
Take this hunt into your environment.
Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.
Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, then reviewed by a person.