Six npm Packages Use Ethereum Transactions to Retrieve Malicious Payloads
Six npm packages were identified delivering a malicious Node.js loader that uses Ethereum blockchain transactions to resolve C2 infrastructure. Three packages were hijacked legitimate projects with malicious code appended to existing files, and three were newly published packages. The loader queries Ethereum RPC providers, decodes IPv4 addresses from transaction recipient addresses, and retrieves secondary payloads from HTTP endpoints. The activity is attributed to the DPRK-linked Contagious Interview campaign.
- filenamedatabase.jsFile in agentgui package where the malicious loader was appended to the last line of an existing legitimate file
- filenameutils.min.jsFile in tailwindcss-motion-advanced package containing the embedded malicious loader
- urlhxxps://eth-mainnet[.]public[.]blastapi[.]ioEthereum RPC provider endpoint used by the loader to query blockchain transactions for C2 resolution; one of multiple RPC providers the malware can query
Detection / Hunteropenrouter
What Happened
Attackers planted malicious code inside six npm software packages that developers download and use in their projects. Three of these packages were legitimate projects that the attackers took over, and three were new packages created to look useful. When someone uses one of these packages, the hidden code contacts the Ethereum blockchain to find instructions about where to download more malicious software. It then downloads and runs that additional software on the victim's computer. This attack is linked to North Korean hackers who have used similar blockchain-based methods before. Organizations should check whether any of these six packages are installed in their systems and remove them, then investigate whether any follow-on malicious activity occurred.
Key Takeaways
- Six npm packages deliver an identical malicious payload that uses Ethereum blockchain transactions as a dead drop to locate C2 infrastructure, a technique dubbed NullReceiver.
- Three legitimate npm packages were hijacked (@kolbo/mcp, agentgui, godot-kit) with malicious code appended to existing files; three additional packages were published specifically to deliver the payload.
- The malware queries multiple Ethereum RPC providers with fallback to the Blockscout API, decodes two IPv4 addresses from transaction recipient addresses, and retrieves secondary payloads from /0x/cls and /0x/ls endpoints.
- Retrieved payloads are Base64- and XOR-decoded, then executed via eval() in the current Node.js process or launched as detached child processes.
- Activity is attributed to the DPRK-linked Contagious Interview campaign associated with the Lazarus APT group, sharing the same Ethereum wallet address as prior OpenSourceMalware findings.
Affected Systems
- Node.js environments with any of the six affected npm packages installed
- Build pipelines and CI/CD systems consuming npm packages
- Developer workstations importing @kolbo/[email protected], [email protected], or [email protected]
- Developer workstations importing [email protected], [email protected], or [email protected]
Vulnerabilities (CVEs)
None identified.
Attack Chain
- Initial Access: Malicious npm packages installed via package hijacking of trusted projects or newly published packages with legitimate-looking functionality
- Execution: Malicious loader code executes when the npm package is imported or used in a Node.js process; loader is appended to existing files in hijacked packages or embedded in utility files
- C2 Resolution: Loader queries multiple Ethereum RPC providers and Blockscout API for outbound transactions from an attacker-controlled wallet, decodes two IPv4 addresses from the transaction recipient address as primary and secondary C2 endpoints
- Payload Retrieval: Loader connects to resolved C2 endpoints at /0x/cls and /0x/ls paths, retrieving encoded payloads via HTTP GET or HEAD requests with X-Payload-B64 header fallback
- Execution: Retrieved payloads are Base64- and XOR-decoded, then executed via eval() in the current Node.js process or launched as detached child processes
Detection Availability
- YARA Rules: No
- Sigma Rules: No
- Snort/Suricata Rules: No
- KQL Queries: No
- Splunk SPL Queries: No
- EQL Queries: No
- Other Detection Logic: No
The article does not provide detection rules, queries, or signatures. It describes the malware behavior and IOCs but does not include YARA, Sigma, Snort, Suricata, KQL, SPL, or EQL rules.
Detection Engineering Assessment
| Dimension | Rating | Rationale |
|---|---|---|
| EDR Visibility | Medium | EDR can observe Node.js processes spawning child processes and making outbound network connections. However, the malicious code runs within the legitimate Node.js runtime, and the use of eval() and child_process may blend with normal Node.js development activity. Unicode escape sequence obfuscation in the loader further reduces visibility into the malicious logic. |
| Network Visibility | Medium | Outbound HTTP connections to resolved C2 IPs at /0x/cls and /0x/ls paths are visible at the network layer. Ethereum RPC API calls to public blockchain providers may appear as legitimate traffic to well-known services. The X-Payload-B64 custom header in HEAD responses is a distinctive network indicator if traffic is inspected. |
| Detection Difficulty | Hard | The malware runs inside the legitimate Node.js runtime, uses Unicode escape obfuscation, queries legitimate public Ethereum RPC endpoints, and retrieves payloads over standard HTTP. The C2 IPs are dynamically resolved from blockchain data, making static blocklists ineffective. Distinguishing malicious Node.js child process spawning from legitimate development activity requires behavioral baselining. |
Required Log Sources
- Process creation and command line logs (Sysmon Event ID 1 or EDR equivalent)
- Network connection logs (Sysmon Event ID 3 or EDR equivalent)
- DNS resolution logs
- HTTP proxy logs with URL path and header inspection
- npm package installation logs and package-lock.json change tracking
- Node.js application logs
Hunting Hypotheses
| Hypothesis | Telemetry | ATT&CK Stage | FP Risk |
|---|---|---|---|
| Consider hunting for Node.js processes making outbound HTTP requests to IP addresses resolved from Ethereum blockchain RPC queries, particularly requests to paths matching /0x/cls or /0x/ls patterns. | Network connection logs, HTTP proxy logs, DNS logs, EDR process network events | C2 and Payload Retrieval | Medium — legitimate development tools may make HTTP requests to various paths, but /0x/ prefixed paths are uncommon in normal Node.js package behavior. |
| Consider hunting for Node.js processes spawning detached child processes shortly after making outbound network connections, which may indicate retrieval and execution of secondary payloads. | EDR process creation events, process ancestry data, Sysmon Event ID 1 | Execution | Medium — Node.js build tools and test runners commonly spawn child processes; correlation with prior network activity to unusual endpoints reduces false positives. |
| Consider hunting for HTTP responses containing a custom X-Payload-B64 header, which the malware uses as a fallback payload delivery mechanism when GET requests fail. | HTTP proxy logs with full header capture, network IDS/IPS with custom header inspection | Payload Retrieval | Low — the X-Payload-B64 header is non-standard and unlikely to appear in legitimate HTTP traffic. |
| Consider hunting for Node.js processes making JSON-RPC calls using eth_getBlockByNumber or similar Ethereum methods to public RPC endpoints, particularly when followed by outbound connections to newly resolved IP addresses. | Network connection logs, HTTP proxy logs, EDR process network events | C2 Resolution | Medium — legitimate blockchain applications make similar RPC calls; correlation with subsequent connections to non-RPC IPs on uncommon paths reduces false positives. |
| Consider hunting for npm package installations or package-lock.json changes involving the six identified package names and versions, particularly in CI/CD pipeline logs. | CI/CD build logs, package manager logs, file integrity monitoring on package-lock.json | Initial Access | Low — the specific package names and versions are identified as malicious; any installation of these exact versions warrants investigation. |
Control Gaps
- Static IP blocklists are ineffective because C2 addresses are dynamically resolved from Ethereum blockchain transaction data.
- Domain-based filtering will not catch this activity because the malware connects directly to IP addresses decoded from blockchain data.
- Standard antivirus may not flag the loader because it runs within the legitimate Node.js runtime and uses Unicode escape obfuscation.
- Network inspection tools that do not capture HTTP response headers will miss the X-Payload-B64 fallback delivery mechanism.
- Package allowlisting based on package name alone will not detect version-level hijacking of trusted packages.
Key Behavioral Indicators
- Node.js process making HTTP requests to /0x/cls or /0x/ls URL paths on resolved IP addresses
- Node.js process querying Ethereum RPC endpoints using eth_getBlockByNumber method followed by outbound connections to non-RPC IPs
- HTTP responses containing custom X-Payload-B64 header
- Node.js process spawning detached child processes after making outbound network connections
- Unicode escape sequence obfuscation in JavaScript files within npm package directories
- npm package-lock.json entries matching the six identified package names and versions
False Positive Assessment
Medium — Node.js processes commonly make network requests and spawn child processes during legitimate development and build activity. The Ethereum RPC calls may appear as legitimate blockchain development traffic. However, the specific URL paths (/0x/cls, /0x/ls), the X-Payload-B64 header, and the correlation between blockchain queries and subsequent IP connections provide distinctive indicators that reduce false positive risk.
Recommendations
Immediate Mitigation
- Verify against your organization's incident response runbook and team escalation paths before acting. Check all development environments, build pipelines, and production systems for the six affected npm packages and remove any installed impacted versions.
- Consider investigating any systems that installed affected packages for signs of follow-on payload execution, including unexpected Node.js child processes, outbound connections to unknown IP addresses, or eval() usage in package code.
- If your package management system supports it, consider adding the six package names and versions to a blocklist to prevent installation in CI/CD pipelines and developer workstations.
- Evaluate whether your npm registry or proxy supports package content scanning and consider enabling it if available to detect obfuscated or appended malicious code in future package updates.
Infrastructure Hardening
- Consider implementing package provenance verification (e.g., npm provenance signatures) where supported to reduce risk of installing hijacked package versions.
- Evaluate whether your network egress filtering can detect and block Node.js processes making direct connections to IP addresses rather than domain names, which is atypical for legitimate npm package behavior.
- If your HTTP inspection tools support custom header rules, consider alerting on responses containing the X-Payload-B64 header.
- Consider monitoring CI/CD pipeline logs for package-lock.json changes involving unexpected version bumps on previously stable packages.
User Protection
- Consider deploying EDR rules that alert on Node.js processes spawning detached child processes after making outbound network connections, if your EDR supports behavioral process correlation.
- Evaluate whether your endpoint tooling can inspect JavaScript files within npm package caches for Unicode escape sequence obfuscation patterns.
- If applicable, consider restricting which Ethereum RPC endpoints developer workstations can reach to reduce the viability of blockchain-based C2 resolution.
Security Awareness
- Consider informing development teams that package hijacking can introduce malicious code into trusted package names through version updates, and that version-level scrutiny is necessary even for familiar packages.
- Consider adding guidance to existing developer security training about reviewing package-lock.json changes for unexpected version bumps before merging dependency updates.
- If your organization uses npm package allowlisting, consider communicating that allowlists should be version-aware and reviewed when packages change ownership or publish unexpected updates.
MITRE ATT&CK Mapping
Initial Access
Execution
Command and Control
Additional IOCs
- Urls:
hxxps://eth-mainnet[.]public[.]blastapi[.]io- Ethereum RPC provider endpoint used by the loader to query blockchain transactions for C2 resolution; one of multiple RPC providers the malware can query
- File Paths:
database.js- File in agentgui package where the malicious loader was appended to the last line of an existing legitimate fileutils.min.js- File in tailwindcss-motion-advanced package containing the embedded malicious loader
- Command Lines:
- Purpose: Obfuscated Node.js loader that queries Ethereum RPC endpoints to resolve C2 infrastructure and retrieve secondary payloads | Tools:
node,child_process,http,https,zlib| Stage: Execution and C2 resolution |require('child_process').exec(
- Purpose: Obfuscated Node.js loader that queries Ethereum RPC endpoints to resolve C2 infrastructure and retrieve secondary payloads | Tools:
- Other:
global.i="A9-2057"- Identifier observed in the obfuscated loader code, visible in the article's code snippet imageprocess.env.ETH_RPC_URL- Environment variable referenced by the loader to specify a custom Ethereum RPC endpointeth_getBlockByNumber- JSON-RPC method used by the loader to query Ethereum blockchain data for C2 resolutionX-Payload-B64- Custom HTTP response header used as a fallback delivery mechanism for encoded payloads when GET requests fail/0x/cls- C2 endpoint path from which the loader retrieves an encoded payload for direct execution via eval() in the current Node.js process/0x/ls- C2 endpoint path from which the loader retrieves additional stages that can be launched as detached Node.js child processes