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
.gitdirectories (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:
- A cryptographic key
kis derived from an obfuscated constant via the custom base16 decoder - The archive is encrypted/encoded using XOR-based stream cipher seeded with HMAC-SHA256
- Data is split into chunks and encoded into DNS subdomain labels
- DNS queries are sent via the
dns.Resolverclass usingresolve4()andresolve6() - The resolver is configured to use
8.8.8.8and1.1.1.1— this is to bypass local DNS filtering/monitoring - The exfiltration domain is derived from another obfuscated constant (
rfield) - HMAC-SHA256 signatures are computed per-chunk for integrity verification
- 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 payloadfork()s a detached child process withstdio: 'ignore'and callsunref()so the parent can exit without waiting - Re-execution guard: Uses
process.env.__ntw/__ntRunflag 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
unlinkSyncin afinallyblock 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
__ntwor__ntRunin environment variables - Temp files
uname.txtorenvs.txtappearing inos.tmpdir()oros.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
- Do not install or use
node-ipc@12.0.1 - 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
- Pin to a known-good version or migrate to an alternative IPC library
- Report to npm security:
npm audit report/ https://www.npmjs.com/advisories