Security News

Cybersecurity news aggregator

HIGH Vulnerabilities Elastic Security Labs

Inside the Axios supply chain compromise - one RAT to rule them all

The Axios npm package was compromised via a maintainer account takeover, leading to the publication of malicious versions 1.14.1 and 0.30.4 which deployed a cross-platform Remote Access Trojan via a postinstall hook. The attack chain downloads platform-specific stage-2 implants from a remote server and performs anti-forensic cleanup to erase evidence. Defenders should immediately check for and remove these specific compromised versions of the axios package.
Read Full Article →

Elastic Security Labs released initial triage and detection rules for the Axios supply-chain compromise. This is a detailed analysis of the RAT and payloads. Introduction Elastic Security Labs identified a supply chain compromise of the axios npm package, one of the most depended-upon packages in the JavaScript ecosystem with approximately 100 million weekly downloads. The attacker compromised a maintainer account and published backdoored versions that delivered a cross-platform Remote Access Trojan to macOS, Windows, and Linux systems through a malicious postinstall hook. Key takeaways A compromised npm maintainer account (jasonsaayman) was used to publish two malicious versions of the widely used Axios HTTP client — 1.14.1 (tagged latest) and 0.30.4 (tagged legacy) — meaning a default npm install axios resolved to a backdoored package The malicious JavaScript deploys platform-specific stage-2 implants for macOS, Windows, and Linux All three stage-2 payloads are implementations of the same RAT — identical C2 protocol, command set, beacon cadence, and spoofed user-agent, written in PowerShell (Windows), C++ (macOS), and Python (Linux) The dropper performs anti-forensic cleanup by deleting itself and swapping its package.json with a clean copy, erasing evidence of the postinstall trigger from node_modules Preamble On March 30, 2026, Elastic Security Labs detected a supply chain compromise targeting the axios npm package through automated supply-chain monitoring. The attacker gained control of the npm account belonging to jasonsaayman, one of the project's primary maintainers, and published two backdoored versions within a 39-minute window. The axios package is one of the most widely depended-upon HTTP client libraries in the JavaScript ecosystem. At the time of discovery, both the latest and legacy dist-tags pointed to compromised versions, ensuring that the majority of fresh installations pulled a backdoored release. The malicious versions introduced a single new dependency: plain-crypto-js, a purpose-built package whose postinstall hook silently downloaded and executed platform-specific stage-2 RAT implants from sfrclak[.]com:8000. What makes this campaign notable beyond its blast radius is the stage-2 tooling. The attacker deployed three parallel implementations of the same RAT — one each for Windows, macOS, and Linux — all sharing an identical C2 protocol, command structure, and beacon behavior. This isn't three different tools; it's a single cross-platform implant framework with platform-native implementations. Elastic Security Labs filed a GitHub Security Advisory to the axios repository on March 31, 2026 at 01:50 AM UTC to coordinate disclosure and ensure the maintainers and npm registry could act on the compromised versions. As the community flagged the compromise on social media, Elastic Security Labs shared early findings publicly to help defenders respond in real time. This post covers the full attack chain: from the npm-level supply chain compromise through the obfuscated dropper, to the architecture of the cross-platform RAT and the meaningful differences between its three variants. Campaign overview The compromise is evident from the npm registry metadata. The maintainer email changed from jasonsaayman@gmail[.]com — present on all prior legitimate releases — to ifstap@proton[.]me on the malicious versions. The publishing method also changed: Version Published By Method Provenance axios@1.14.0 (legitimate) jasonsaayman@gmail[.]com GitHub Actions OIDC SLSA provenance attestations axios@1.14.1 (compromised) ifstap@proton[.]me Direct CLI publish None axios@0.30.4 (compromised) ifstap@proton[.]me Direct CLI publish None The shift from a trusted OIDC publisher flow with SLSA provenance to a direct CLI publish with a changed email is a clear indicator of unauthorized access. Timeline 2026-02-18 17:19 UTC — axios@0.30.3 published legitimately by jasonsaayman@gmail[.]com 2026-03-27 19:01 UTC — axios@1.14.0 published legitimately via GitHub Actions OIDC 2026-03-30 05:57 UTC — plain-crypto-js@4.2.0 published by nrwise ( nrwise@proton.me ) — clean decoy to build registry history 2026-03-30 23:59 UTC — plain-crypto-js@4.2.1 published by nrwise — malicious version with postinstall backdoor 2026-03-31 00:21 UTC — axios@1.14.1 published by compromised account — tagged latest 2026-03-31 01:00 UTC — axios@0.30.4 published by compromised account — tagged legacy Affected packages axios@1.14.1 — Malicious, tagged latest at time of discovery axios@0.30.4 — Malicious, tagged legacy at time of discovery plain-crypto-js@4.2.0 — Clean decoy, published to build registry history plain-crypto-js@4.2.1 — Malicious, payload delivery vehicle ( postinstall backdoor) Safe versions: axios@1.14.0 (last legitimate 1.x release with SLSA provenance) and axios@0.30.3 (last legitimate 0.30.x release). The attacker tagged both the latest and legacy channels, maximizing the blast radius across projects using either the current or legacy axios API. Code analysis Stage 1: The plain-crypto-js dropper The entire delivery chain hinges on npm's postinstall lifecycle hook. Installing either compromised axios version pulls plain-crypto-js@^4.2.1 as a dependency, which declares: "scripts": { "postinstall": "node setup.js" } This causes setup.js to execute automatically during npm install — no user interaction required. The setup.js file uses a two-layer encoding scheme to conceal its behavior: Layer 1: String reversal followed by Base64 decoding Layer 2: XOR cipher using the key OrDeR_7077 with a position-dependent index (7 * i² % 10) All critical strings, module names, URLs, shell commands are stored in an encoded array stq[] and decoded at runtime. The decoded contents reveal the operational infrastructure: Platform-specific delivery After decoding its string table, the dropper checks os.platform() and branches into one of three delivery routines. Each sends an HTTP POST to http://sfrclak[.]com:8000/6202033 with a platform-specific body — packages.npm.org/product0 (macOS), packages.npm.org/product1 (Windows), packages.npm.org/product2 (Linux) — allowing the C2 to serve the correct payload from a single endpoint. The packages.npm.org/ prefix is a deliberate attempt to make outbound traffic appear as benign npm registry communication in network logs: Platform Delivery Method Stage-2 Location Disguise macOS AppleScript via osascript downloads binary with curl /Library/Caches/com.apple.act.mond Apple system daemon Windows VBScript downloads .ps1 via curl, executes via renamed PowerShell ( %PROGRAMDATA%\wt.exe ) %TEMP%\6202033.ps1 (transient) Windows Terminal Linux Direct curl download and python3 execution /tmp/ld.py None Anti-forensics The dropper performs two cleanup actions: Self-deletion: setup.js removes itself via fs.unlink(__filename) Package manifest swap: A clean file named package.md (containing a benign version 4.2.0 configuration with no postinstall hook) is renamed to package.json, overwriting the malicious version Post-incident inspection of node_modules/plain-crypto-js/package.json reveals no trace of the postinstall trigger. The malicious setup.js is gone. Only the lockfile and npm audit logs retain evidence. Stage 2: Cross-platform RAT The three stage-2 payloads: PowerShell for Windows, compiled C++ for macOS, Python for Linux are not three different tools. They are three implementations of the same RAT specification , sharing an identical C2 protocol, command set, message format, and operational behavior. The consistency strongly indicates a single developer or tightly coordinated team working from a shared design document. Shared architecture The following properties are identical across all three variants: C2 transport: HTTP POST Body encoding: Base64-encoded JSON User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) Beacon interval: 60 seconds Session UID: 16-character random alphanumeric string, generated per-execution Outbound message types: FirstInfo , BaseInfo , CmdResult Inbound command types: kill , peinject , runscript , rundir Response command types: rsp_kill , rsp_peinject , rsp_runscript , rsp_rundir The spoofed IE8/Windows XP user-agent string is particularly notable, it is anachronistic on all three platforms, and its presence on a macOS or Linux host is a strong detection indicator. Initialization and reconnaissance On startup, each variant: Generates a session UID — 16 random alphanumeric characters, included in every subsequent C2 message Detects OS and architecture — reports platform-specific identifiers (e.g., windows_x64, macOS, linux_x64) Enumerates initial directories of interest (user profile, documents, desktop, config directories) Sends a FirstInfo beacon containing the UID, OS identifier, and directory snapshot After initialization, the implant enters the main loop. The first BaseInfo heartbeat includes a comprehensive system profile. The same categories of data are collected on all platforms, though the underlying APIs differ: Data Collected Windows Source macOS Source Linux Source Hostname %COMPUTERNAME% env var gethostname() /proc/sys/kernel/hostname Username %USERNAME% env var getuid() + getpwuid() os.getlogin() OS version WMI / registry sysctlbyname("kern.osproductversion") platform.system() + platform.release() Timezone System timezone localtime_r() datetime.timezone Boot time System uptime sysctl("kern.boottime") /proc/uptime Install date Registry / WMI stat("/") or sysctl ctime of /var/log/installer or /var/log/dpkg.log Hardware model WMI sysctlbyname("hw.model") /sys/class/dmi/id/product_name CPU type WMI sysctlbyname() platform.machine() Process list Full PID, session, name, path popen("ps") (up to 1000) Full /proc enumeration (PID, PPID, user, cmdline) Subsequent heartbeats are lightweight, containing only a timestamp to confirm the implant is alive. Command dispatch The C2 response is parsed as JSON, and the type field determines the action. All three variants implement the same four commands: kill — Self-termination. Sends an rsp_kill acknowledgment and exits. The Windows variant's persistence mechanism (registry key + batch file) survives the kill command unless explicitly cleaned up; the macOS and Linux variants have no persistence of their own. runscript — Script/command execution. The operator's primary interaction command. Accepts a Script field (code to execute) and a Param field (arguments). When Script is empty, Param is run directly as a command. The execution mechanism is platform-native: Platform Execution Mechanism Windows PowerShell with -NoProfile -ep Bypass macOS AppleScript via /usr/bin/osascript Linux Shell via subprocess.run(shell=True) or Python via python3 -c peinject — Binary payload delivery. Despite the Windows-centric naming ("PE inject"), all three platforms implement this as a way to drop and execute binary payloads: Platform Implementation Windows Reflective .NET assembly loading via [System.Reflection.Assembly]::Load() macOS Base64-decodes and drops a binary, executes with operator-supplied parameters. Linux Base64-decodes a binary to /tmp/.<random 6-char string> (hidden file), launches via subprocess.Popen(). The Windows implementation has in-memory execution with no file drop but without disabling AMSI which will certainly flag on the Assembly load. The macOS and Linux variants take the simpler approach of writing a binary to disk and executing it directly. rundir — Directory enumeration. Accepts paths and returns detailed file listings (name, size, type, creation/modification timestamps, child count for directories). Allows the operator to interactively browse the filesystem. Capability summary Capability Windows (PowerShell) macOS (C++) Linux (Python) Persistence Registry Run key + hidden .bat None None Script execution PowerShell AppleScript via osascript Shell or Python inline Binary injection Reflective .NET load injecting into cmd.exe Binary drop + execute Binary drop to /tmp/ + execute Anti-forensics Hidden windows, temp file cleanup Hidden temp .scpt Hidden /tmp/.XXXXXX files Attribution The macOS Mach-O binary delivered by the plain-crypto-js postinstall hook exhibits significant overlap with WAVESHAPER , a C++ backdoor tracked by Mandiant and attributed to UNC1069 , a DPRK-linked threat cluster. Conclusion This campaign demonstrates the continued attractiveness of the npm ecosystem as a supply chain attack vector. By compromising a single maintainer account on one of the JavaScript ecosystem's most depended-upon packages, the attacker gained a delivery mechanism with potential reach into millions of environments. The toolkit's most reliable detection indicator is also its most curious design choice: the IE8/Windows XP user-agent string hardcoded identically across all three platform variants. While it provides a consistent protocol fingerprint for C2 server-side routing, it is trivially detectable on any modern network — and is an immediate anomaly on macOS and Linux hosts. Elastic Security Labs will continue monitoring this activity cluster and will update this post with any additional findings. MITRE ATT&CK Elastic uses the MITRE ATT&CK framework to document common tactics, techniques, and procedures that advanced persistent threats use against enterprise networks. Tactics Tactics represent the why of a technique or sub-technique. It is the adversary’s tactical goal: the reason for performing an action. Initial Access Execution Persistence Defense Evasion Discovery Command and Control Techniques Techniques represent how an adversary achieves a tactical goal by performing an action. Supply Chain Compromise: Compromise Software Dependencies Command and Scripting Interpreter: JavaScript Command and Scripting Interpreter: PowerShell Command and Scripting Interpreter: AppleScript Command and Scripting Interpreter: Unix Shell Command and Scripting Interpreter: Python Boot or Logon Autostart Execution: Registry Run Keys Obfuscated Files or Information Masquerading Hidden Files and Directories Process Injection Indicator Removal: File Deletion System Information Discovery Process Discovery File and Directory Discovery Application Layer Protocol: Web Protocols Non-Standard Port Data Encoding: Standard Encoding Ingress Tool Transfer Observations The following observables were discussed in this research. Observable Type Name Reference 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 SHA-256 6202033.ps1 Windows payload 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a SHA-256 com.apple.act.mond MacOS payload fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf SHA-256 ld.py Linux payload sfrclak[.]com DOMAIN C2 142.11.206[.]73 ipv4-addr C2 References The following were referenced throughout the above research: https://www.elastic.co/security-labs/axios-supply-chain-compromise-detections

Share this article