[SECURITY][REPORT] node-ipc@12.0.1 CJS bundle contains obfuscated infostealer payload

5 min read Original article ↗

issue fixed by NPM valid report will be made for public notice

task status contributor
investigate validity @ashishkurmi @cyb3rjerry @Roni-Carta @danielweck @carlini @RIAEvangelist
collect intel @carlini @jimmytsadiotis @ashishkurmi @cyb3rjerry @Roni-Carta @danielweck @RIAEvangelist
CONFIRM this version was released on the official version or not @Roni-Carta @RIAEvangelist
HOW THE FACT did that happen without publish rights? Is something missing in the security process? @RIAEvangelist
Contact NPM with suggestion on securioty feature to update maintainer of package whenever a new version is published, I got no email on this version being released @RIAEvangelist
begin report generation @ashishkurmi @carlini
finalize report @RIAEvangelist
report

Above edit by RIAEvangelist
Below by OP


OP post about vulnerability a contributors email appears to have been compromised and used to publish

The CommonJS entry point (node-ipc.cjs) in node-ipc@12.0.1 (npm shasum: fe5d107b9d285327af579259a32977c4f475fa26) contains a heavily obfuscated malicious payload appended after the legitimate esbuild-generated bundle. This payload is an infostealer that harvests system information, environment variables (which often contain secrets/API keys), and sensitive files, then exfiltrates them via DNS tunneling.

The ESM entry point (node-ipc.js) and all other source files in the package are clean. The malicious code exists only in node-ipc.cjs, appended as an IIFE after the legitimate module.exports at the end of the file.

Affected Version

  • Package: node-ipc
  • Version: 12.0.1
  • Registry: npm
  • Integrity: sha512-GTPCDJ2M7lC69[...]HwUAhXYdKQ0Vg==
  • Shasum: fe5d107b9d285327af579259a32977c4f475fa26

Technical Analysis

Location of malicious code

The legitimate CJS bundle ends at the module.exports statement. Immediately after, a self-executing obfuscated function is appended:

// Legitimate code ends here:
0 && (module.exports = {
  IPCModule
});

// Malicious payload begins — obfuscated IIFE:
(function(_0xaed59b,_0x282d65){var _0x4524e4=_0x1a49, ...

Obfuscation techniques used

  • Hex-encoded variable/function names (_0x4524e4, _0x3ecdb0, etc.)
  • String array with rotation (_0x3afe() returns a shuffled string array, accessed via _0x1a49() index function)
  • Control flow flattening ('5|1|4|3|0|2'.split('|') switch dispatch pattern)
  • Encoded string literals for sensitive API names to avoid static detection

Capability 1: System reconnaissance

The payload collects:

Data Method
OS platform, arch, hostname, homedir, tmpdir os module (os.platform(), os.arch(), os.hostname(), etc.)
Full uname -a output child_process.execSync('uname -a')
Process PID, CWD process.pid, process.cwd()
All environment variables Object.keys(process.env) — iterated and joined
/etc/hosts contents fs.readFileSync('/etc/hosts')

Collected data is written to temp files (uname.txt, envs.txt) and included in an archive.

Capability 2: Sensitive file harvesting

The payload contains encoded glob/path patterns (obfuscated via a custom base16-like encoding using the charset 0123456789GHJKMP) that, when decoded, target sensitive files including but not limited to:

  • SSH keys and configuration
  • Shell history and profiles
  • Cloud provider credentials and config files
  • Browser profile data
  • Application configuration files with potential secrets
  • .git directories (credentials, config)

Files are read via fs.readFileSync(), and the payload checks statSync().isFile() and enforces a size limit before collection.

Capability 3: Archive creation

Harvested files are packaged into a tar archive (custom implementation using raw Buffer manipulation with proper tar header format — ustar\0 magic bytes, 0000644 permissions, octal size encoding) and then compressed with zlib.gzipSync().

Capability 4: DNS-based data exfiltration (C2)

The payload exfiltrates the archive using DNS tunneling:

  1. A cryptographic key k is derived from an obfuscated constant via the custom base16 decoder
  2. The archive is encrypted/encoded using XOR-based stream cipher seeded with HMAC-SHA256
  3. Data is split into chunks and encoded into DNS subdomain labels
  4. DNS queries are sent via the dns.Resolver class using resolve4() and resolve6()
  5. The resolver is configured to use 8.8.8.8 and 1.1.1.1 — this is to bypass local DNS filtering/monitoring
  6. The exfiltration domain is derived from another obfuscated constant (r field)
  7. HMAC-SHA256 signatures are computed per-chunk for integrity verification
  8. A header/data/footer protocol is used: header chunks contain metadata (machine ID, chunk counts, archive path), data chunks contain the encrypted file content, and a footer signals completion

Capability 5: Stealth and persistence

  • Detached child process: When the entry script is require.main, the payload fork()s a detached child process with stdio: 'ignore' and calls unref() so the parent can exit without waiting
  • Re-execution guard: Uses process.env.__ntw / __ntRun flag to prevent running the payload twice
  • Filename check: Computes sha256(path.basename(require.main.filename)) and compares against a hardcoded hash — this is a targeting mechanism to only activate in specific contexts
  • Cleanup: Temporary archive file is deleted with unlinkSync in a finally block after exfiltration
  • Silent failure: All operations are wrapped in try/catch to avoid crashing the host application

Indicators of Compromise (IOCs)

Strings found in the obfuscated code

uname.txt
envs.txt
/etc/hosts
8.8.8.8
1.1.1.1
ustar\0
0000644\0
.git
node_modules
darwin
linux
child_process
execSync
writeFileSync
readFileSync
readdirSync
statSync
mkdirSync
gzipSync
createHash
createHmac
randomBytes
Resolver
setServers
resolve4
resolve6
resolveTxt
fork

Obfuscated key constants

_0x4106eb = '17G58307J43367M487259377H4K645978426653664764437G41654P655966'
_0x5c99b0  (resolver/domain constant)
_0x596159  (path pattern constant)
_0x5c21f0  (platform-specific path pattern arrays)

Behavioral indicators

  • Unexpected DNS TXT/A/AAAA queries with long subdomain labels from Node.js processes
  • Presence of __ntw or __ntRun in environment variables
  • Temp files uname.txt or envs.txt appearing in os.tmpdir() or os.homedir()
  • A directory created at path.join(os.homedir(), 'nt-<hash>')

Reproduction

npm pack node-ipc@12.0.1 --pack-destination .
tar -xzf node-ipc-12.0.1.tgz
# Inspect the last ~1000 characters of node-ipc.cjs — obfuscated payload is visible
tail -c 5000 package/node-ipc.cjs

Recommended Actions

  1. Do not install or use node-ipc@12.0.1
  2. If already installed, treat the system as compromised:
    • Rotate all secrets, API keys, and tokens that were present in environment variables
    • Rotate SSH keys
    • Audit DNS query logs for anomalous subdomain-encoded queries
    • Check for the nt-* directory in user home directories
  3. Pin to a known-good version or migrate to an alternative IPC library
  4. Report to npm security: npm audit report / https://www.npmjs.com/advisories