Astrill VPN Egress on Corporate Authentication
Identity / cloudWindowsmacOS
IOC Hunt medium DPRK IT Worker FraudFamous Chollima
Astrill VPN is the anonymisation service most consistently associated with DPRK remote-worker fraud. This matches corporate sign-ins against its published exit addresses.
What this hunt looks for
Astrill VPN appears in nearly every substantial report on this threat. Mandiant tags fourteen published addresses as Astrill exits, Unit 42 notes its popularity in China, and ReliaQuest observes that operators expose themselves when the tunnel drops, appearing one day through Astrill and the next on a Russian ISP. Spur has published a free list of roughly 2,400 addresses.
This is a commercial consumer VPN with an ordinary customer base, so an exit address is a reason to look, never a finding alone. Mandiant tags one Astrill address under Cogent (AS174), a transit network too broad to hunt on as an autonomous system; only the specific addresses apply.
- Technique
- Proxy/VPN egress obscuring the true origin of a session (MITRE ATT&CK T1090.003) in support of fraudulently obtained employment (T1078).
- Data sources
- Identity provider sign-in logs, EDR process telemetry, Software inventory
- Last updated
- 2026-10-04
Indicators
The atomic values this hunt matches on, listed separately from the queries so they can be lifted into tooling that speaks none of the query languages below. Every value here is reported by one of the sources cited at the foot of this page.
| Type | Value | Note |
|---|---|---|
| IP | 104.206.40.138 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 104.250.148.58 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 174.128.251.99 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 192.119.10.67 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 192.119.11.250 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 192.74.247.161 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 198.135.49.154 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 198.2.228.20 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 199.115.99.34 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 204.188.232.195 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 38.140.49.92 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 70.39.103.3 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 74.222.20.18 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| IP | 74.63.233.50 |
Tagged AstrillVPN in Mandiant's published indicator table (2024-09-23). |
| Feed | https://storage.googleapis.com/spur-astrill-vpn/ips.txt |
Spur's free list of roughly 2,400 Astrill addresses, published 2024-12-19. A dated snapshot, not a maintained feed; refresh from current intelligence. |
Logic
Every query below implements these steps, so the same hunt returns the same result in each product.
- Collect: Entra ID and Okta sign-ins (interactive and non-interactive); optionally Windows and macOS process starts
- Match: sign-in source address is one of the 14 Astrill exits Mandiant published, or is on the Spur Astrill list (watchlist or lookup)
- Match: or (optional, weak) the Astrill client runs on an endpoint (astrill.exe, Astrill.app)
- Report: users and source addresses with sign-in counts, newest first
Queries
Defender / Sentinel KQL
Written for this site, from indicators reported by Mandiant, SpurRows are users (and, if the optional branch is kept, devices) with Astrill sign-ins or client executions; a sign-in match is a reason to look, never a finding alone.
// Hunt: Astrill VPN egress on corporate sign-ins (Microsoft Entra ID).
let AstrillIPs = dynamic(["104.206.40.138", "104.250.148.58", "174.128.251.99", "192.119.10.67", "192.119.11.250", "192.74.247.161", "198.135.49.154", "198.2.228.20", "199.115.99.34", "204.188.232.195", "38.140.49.92", "70.39.103.3", "74.222.20.18", "74.63.233.50"]);
// optional: the Spur list (https://storage.googleapis.com/spur-astrill-vpn/ips.txt) loaded into a Sentinel watchlist named AstrillVPN with the address as SearchKey; exits rotate, so refresh the watchlist
let SpurIPs = _GetWatchlist("AstrillVPN") | project IPAddress = tostring(SearchKey);
union
// optional: the Astrill client on an endpoint shows installation, not use against corporate resources
( DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("astrill.exe", "Astrill") or FolderPath contains "\\astrill\\" or FolderPath contains "/Applications/Astrill.app/"
| project Timestamp, Subject = DeviceName, Signal = "astrill-client", Address = "", Success = true, Evidence = FolderPath ),
( union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where IPAddress in (AstrillIPs)
// optional: the Spur list watchlist
or IPAddress in (SpurIPs)
| project Timestamp = TimeGenerated, Subject = UserPrincipalName, Signal = "astrill-signin", Address = IPAddress, Success = ResultType == "0", Evidence = AppDisplayName )
| summarize Events = count(), Successes = countif(Success), Addresses = make_set(Address), Apps = make_set(Evidence, 10),
FirstSeen = min(Timestamp), LastSeen = max(Timestamp), Signals = make_set(Signal) by Subject
| sort by LastSeen desc
Lower-fidelity parts (2)
- the Spur watchlist lines — the query fails if no AstrillVPN watchlist exists, and the list is a dated snapshot. Remove: delete the two optional lines that define SpurIPs and the two lines that test IPAddress in (SpurIPs)
- the DeviceProcessEvents branch — an installed Astrill client shows installation rather than use against corporate resources. Remove: delete from the // optional: the Astrill client line through the closing `),` of that branch
Sigma
Written for this site, from indicators reported by Spur, MandiantCorroborating rule for the optional endpoint match: the Astrill client alone is weak evidence and should not alert on its own.
# optional: this whole rule is the weak endpoint match; do not alert on it alone
title: Astrill VPN Client Executed on a Corporate Endpoint
id: 2d86b7a4-1c3f-4a59-8e1e-93a0c5d7f6b2
status: experimental
description: >
Detects the Astrill VPN client running on a Windows or macOS endpoint. Astrill
appears in nearly every substantial report on DPRK remote-worker fraud, but it
is a commercial consumer VPN, so an installed client is weak evidence that
corroborates an identity-side match on its exit addresses and proves nothing alone.
references:
- https://spur.us/blog/astrill-vpn-and-dprk-remote-worker-fraud/
- https://cloud.google.com/blog/topics/threat-intelligence/mitigating-dprk-it-worker-threat
logsource:
category: process_creation
detection:
selection_name:
Image|endswith:
- '\astrill.exe'
- '/Astrill'
selection_path:
Image|contains:
- '\astrill\'
- '/Applications/Astrill.app/'
condition: 1 of selection_*
falsepositives:
- An employee running Astrill personally, notably where the internet is filtered
level: low
Lower-fidelity parts (1)
- the whole rule — an installed Astrill client is weak evidence, since an employee may run it personally. Remove: do not schedule it as an alert; use it only to corroborate an identity-side match
Splunk SPL
Written for this site, from indicators reported by Mandiant, SpurSign-ins from the published exit addresses per user and address; the subsearch adds the Spur list from a lookup.
```Placeholder: index=identity holds Okta, Entra ID or ms:aad:signin events with src_ip, user and app extracted.```
index=identity (sourcetype=okta OR sourcetype=azuread OR sourcetype="ms:aad:signin")
(src_ip IN ("104.206.40.138", "104.250.148.58", "174.128.251.99", "192.119.10.67", "192.119.11.250", "192.74.247.161", "198.135.49.154", "198.2.228.20", "199.115.99.34", "204.188.232.195", "38.140.49.92", "70.39.103.3", "74.222.20.18", "74.63.233.50")
```optional: the Spur list (https://storage.googleapis.com/spur-astrill-vpn/ips.txt) loaded as the lookup astrill_vpn_ips.csv with an ip column; exits rotate, so refresh the lookup```
OR [| inputlookup astrill_vpn_ips.csv | rename ip AS src_ip | fields src_ip])
| stats count AS signins min(_time) AS first_seen max(_time) AS last_seen values(app) AS apps BY user, src_ip
| convert ctime(first_seen) ctime(last_seen)
| sort - last_seen
Lower-fidelity parts (1)
- the inputlookup subsearch — the search fails if the lookup does not exist, and the list is a dated snapshot. Remove: delete the ```optional comment and the OR [| inputlookup ...] line, keeping the closing bracket of the group
CrowdStrike LogScale
Written for this site, from indicators reported by MandiantFree-text match of the 14 addresses over ingested Entra ID or Okta events; user is extracted from userPrincipalName or alternateId when present.
// Hunt: Astrill VPN egress on corporate sign-ins. The address is matched as a whole token in the raw event, so no parser field name is assumed.
regex("(?:^|[^0-9.])(?<matchedIp>104[.]206[.]40[.]138|104[.]250[.]148[.]58|174[.]128[.]251[.]99|192[.]119[.]10[.]67|192[.]119[.]11[.]250|192[.]74[.]247[.]161|198[.]135[.]49[.]154|198[.]2[.]228[.]20|199[.]115[.]99[.]34|204[.]188[.]232[.]195|38[.]140[.]49[.]92|70[.]39[.]103[.]3|74[.]222[.]20[.]18|74[.]63[.]233[.]50)(?:[^0-9.]|$)")
| regex("(?:userPrincipalName|alternateId)[^a-zA-Z0-9_]+(?<user>[^\"]+)", strict=false)
| groupBy([matchedIp, user], function=[count(as=events), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen)])
| sort(lastSeen, order=desc)
Elastic
Written for this site, from indicators reported by MandiantSign-ins from the published exit addresses per account and address over the Entra ID and Okta integrations; check which user field your integration version fills.
FROM logs-azure.signinlogs-*, logs-okta*
| WHERE @timestamp > NOW() - 30 days
| EVAL src = TO_STRING(source.ip)
| WHERE src IN ("104.206.40.138", "104.250.148.58", "174.128.251.99", "192.119.10.67", "192.119.11.250", "192.74.247.161", "198.135.49.154", "198.2.228.20", "199.115.99.34", "204.188.232.195", "38.140.49.92", "70.39.103.3", "74.222.20.18", "74.63.233.50")
| EVAL account = COALESCE(azure.signinlogs.properties.user_principal_name, user.name)
| EVAL succeeded = CASE(event.outcome == "success", 1, 0)
| STATS sign_ins = COUNT(*), successes = SUM(succeeded), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY account, src
| SORT last_seen DESC
osquery
Written for this site, from indicators reported by SpurLocal check for the Astrill client running or installed, on Windows and macOS.
-- optional: this whole query is the weak endpoint match (the Astrill client on this Windows or macOS endpoint); do not alert on it alone
SELECT 'running' AS signal, name AS detail, path
FROM processes
WHERE lower(name) IN ('astrill', 'astrill.exe') OR path LIKE '%/Applications/Astrill.app/%' OR lower(path) LIKE '%\astrill\%'
UNION ALL
SELECT 'installed', filename, path
FROM file
WHERE path = '/Applications/Astrill.app'
Lower-fidelity parts (1)
- the whole query — an installed Astrill client is weak evidence, since an employee may run it personally. Remove: do not schedule it as an alert; use it only to corroborate an identity-side match
False positives and tuning
Astrill is a commercial VPN with a legitimate customer base, notably where the internet is filtered, so a match warrants investigation, never a finding alone. Huntress notes infrastructure is reused; correlate with access patterns.
Keep the address lists, which are snapshots, in a watchlist or lookup rather than inline. Do not hunt on Mandiant's autonomous systems as a class: several are general hosting, and one address sits under Cogent.
An installed Astrill client is weak evidence, since an employee may run it personally; use the endpoint queries only to corroborate an identity-side match.
Sources
- Astrill VPN and DPRK Remote Worker Fraud — Spur, 2024-12-19
- Staying a Step Ahead: Mitigating the DPRK IT Worker Threat — Google Cloud (Mandiant), 2024-09-23
- Threat Spotlight: Red Flags for Red Star Hackers: Hunting for North Korean Insiders — ReliaQuest, 2025-05-08
- Insights into Suspected DPRK Workers: Red Flags to Look Out For — Huntress, 2026-08-26
- Global Companies Are Unknowingly Paying North Koreans: Here's How to Catch Them — Unit 42 (Palo Alto Networks), 2024-11-13
- Famous Chollima Threat Actor Profile — Huntress Threat Library