WaterPlum / Contagious Interview – 30,000 Devices Compromised Through Fake Developer Recruiting

Threat Overview

A September 18 joint government advisory, reported September 19, attributes a large global campaign to North Korean-linked WaterPlum, also associated with Contagious Interview and Deceptive Development. Authorities report at least 30,000 infected devices in more than 100 countries and theft involving more than 7,000 cryptocurrency wallets. Attackers impersonate legitimate AI, cryptocurrency, and NFT companies or use recruiting and freelance platforms, then convince developers to download projects or execute malicious code. Malicious npm packages and developer tooling are recurring delivery mechanisms.

References

Impacted Systems

Developer workstations and endpoints that execute untrusted npm/Node.js projects or coding-test repositories. Windows, macOS, and Linux can be relevant depending on payload. This is social-engineering driven rather than a single vulnerable software version.

Why this matters

The campaign targets developers whose systems often contain source-code access, cloud credentials, SSH keys, registry tokens, and cryptocurrency assets.

Exploitation Status

Confirmed campaign. Authorities report activity from December 2025 through July 2026 and at least 30,000 compromised devices. The September 18 advisory is new intelligence; the tradecraft itself predates it.

What this hunt looks for

Developer tooling spawning shells, package-manager install scripts, unusual Node network activity, executable/script creation, credential-path interaction, and subsequent identity anomalies.

Required logs

Defender XDR DeviceProcessEvents, DeviceNetworkEvents and DeviceFileEvents, plus Microsoft Entra SigninLogs where available.

First Pass – Developer Tooling Spawning Shells

DeviceProcessEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("code.exe","code","node.exe","node","npm.exe","npm","npx.exe","npx","yarn.exe","yarn","pnpm.exe","pnpm")
| where FileName in~ ("powershell.exe","pwsh.exe","cmd.exe","bash","sh","curl","wget","python","python3")
| project Timestamp,DeviceName,AccountName,InitiatingProcessFileName,InitiatingProcessCommandLine,FileName,ProcessCommandLine,SHA256
| order by Timestamp desc

Package Manager Install Scripts

DeviceProcessEvents
| where Timestamp >= ago(30d)
| where FileName in~ ("npm.exe","npm","npx.exe","npx","yarn.exe","yarn","pnpm.exe","pnpm","node.exe","node")
| where ProcessCommandLine has_any ("install","postinstall","preinstall","prepare")
| project Timestamp,DeviceName,AccountName,FileName,ProcessCommandLine,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Developer Tool Network Activity

DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node.exe","node","npm.exe","npm","npx.exe","npx","code.exe","code")
| project Timestamp,DeviceName,InitiatingProcessAccountName,InitiatingProcessFileName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

New Destinations From Node

let Known=DeviceNetworkEvents
| where Timestamp between (ago(30d)..ago(2d))
| where InitiatingProcessFileName in~ ("node.exe","node")
| summarize by DeviceId,RemoteUrl,RemoteIP;
DeviceNetworkEvents
| where Timestamp >= ago(2d)
| where InitiatingProcessFileName in~ ("node.exe","node")
| join kind=leftanti Known on DeviceId,RemoteUrl,RemoteIP
| project Timestamp,DeviceName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

Node Creating Executables and Scripts

DeviceFileEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node.exe","node","npm.exe","npm","npx.exe","npx")
| where ActionType =~ "FileCreated"
| where FileName endswith ".exe" or FileName endswith ".dll" or FileName endswith ".ps1" or FileName endswith ".sh" or FileName endswith ".js"
| project Timestamp,DeviceName,FileName,FolderPath,SHA256,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Credential and Wallet Path Access

DeviceFileEvents
| where Timestamp >= ago(30d)
| where InitiatingProcessFileName in~ ("node.exe","node","python.exe","python","python3")
| where FolderPath has_any (".ssh",".aws",".azure","AppData\\Roaming","Library/Application Support")
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Risky Entra Sign-Ins

SigninLogs
| where TimeGenerated >= ago(30d)
| where RiskLevelDuringSignIn !in~ ("none","hidden","")
| project TimeGenerated,UserPrincipalName,IPAddress,AppDisplayName,RiskLevelDuringSignIn,RiskState,RiskDetail,LocationDetails,DeviceDetail
| order by TimeGenerated desc

New Sign-In IPs

let Known=SigninLogs
| where TimeGenerated between (ago(30d)..ago(2d))
| summarize by UserPrincipalName,IPAddress;
SigninLogs
| where TimeGenerated >= ago(2d)
| join kind=leftanti Known on UserPrincipalName,IPAddress
| project TimeGenerated,UserPrincipalName,IPAddress,AppDisplayName,ClientAppUsed,ResultType,LocationDetails,DeviceDetail
| order by TimeGenerated desc

Detection Notes

Developer environments generate substantial Node and shell noise. Highest-value signals are unexpected shell execution, new external destinations, credential-file access, and subsequent identity anomalies. Exact payload IOCs can change across campaign waves.

Leave a comment