Back to reports

Nine npm Packages, One Open Directory, and a Cryptocurrency Exchange Stolen Keys: Inside Operation HEXSTRIKE

A supply chain attack targeting Strapi CMS developers compromised an Estonian cryptocurrency exchange. The attacker left their entire toolkit in an open directory.

PublishedApril 3, 2026

At 3:47 PM UTC on April 3, 2026, a user named umarbek1233 published nine npm packages in rapid succession. All nine were typosquats of legitimate Strapi CMS plugins. All nine contained a postinstall script that deployed a multi-phase C2 agent. And the C2 server they called home to had its entire toolkit -- source code, exploit scripts, credential stuffing tools, and an exfiltration log -- sitting in an open directory for anyone to download.

We downloaded all of it.

The Packages

Nine packages. Two hours. One throwaway account registered to cla4d@sharebot[.]net:

The nine packages:

PackagePublished
strapi-plugin-cron02:02 UTC
strapi-plugin-config02:47 UTC
strapi-plugin-server03:01 UTC
strapi-plugin-database03:05 UTC
strapi-plugin-core03:06 UTC
strapi-plugin-hooks03:37 UTC
strapi-plugin-monitor03:40 UTC
strapi-plugin-events03:46 UTC
strapi-plugin-logger03:58 UTC

All nine follow the same pattern: single version, three files (package.json, postinstall.js, index.js), a postinstall hook that executes immediately on npm install, and a C2 callback to 144[.]31[.]107[.]231:9999 over unencrypted HTTP.

The naming targets Strapi developers specifically -- strapi-plugin-events, strapi-plugin-config, and similar variations of real Strapi ecosystem packages. A developer adding a dependency with a typo or copy-pasting from a poisoned guide would trigger the full attack chain without any user interaction beyond npm install.

The 11-Phase Attack

The postinstall script executes an 11-phase reconnaissance and exfiltration sequence:

  1. Environment harvesting -- Dumps all environment variables, targeting database credentials, API keys, JWT secrets
  2. .env file theft -- Reads .env, .env.local, .env.production from the project root and parent directories
  3. Strapi config exfiltration -- Parses config/database.js, config/server.js, config/admin.js for connection strings and secrets
  4. Redis key dumping -- Opens raw TCP connections to localhost:6379 and sends KEYS * followed by GET for each key
  5. Docker secrets -- Reads /run/secrets/* for Docker swarm secrets
  6. Kubernetes tokens -- Steals /var/run/secrets/kubernetes.io/serviceaccount/token and CA certificates
  7. Private key discovery -- Recursively searches for *.pem, *.key, *.p12, *.pfx files
  8. Cryptocurrency wallet theft -- Searches for wallet files, seed phrases, and key stores
  9. SSH key theft -- Reads ~/.ssh/id_rsa, ~/.ssh/id_ed25519, and known_hosts
  10. Git credential theft -- Parses .git-credentials, .gitconfig for stored tokens
  11. C2 polling loop -- Opens a 5-minute polling connection to 144[.]31[.]107[.]231:9999 accepting remote shell commands

No obfuscation. No encryption. Plain HTTP. The operator either doesn't care about detection or is moving fast enough that detection doesn't matter.

The Open Directory

Port 8888 on the C2 server runs Python's SimpleHTTPServer with directory listing enabled. The attacker's entire working directory is exposed. We recovered 52 files including:

C2 Infrastructure

  • C2 server source code (the listener that receives stolen credentials)
  • Client agent templates (the code injected into npm packages)
  • Configuration files with hardcoded paths and victim identifiers

Post-Exploitation Toolkit

  • CVE-2023-22621 exploits -- Three variants of the Strapi Server-Side Template Injection vulnerability, designed to escalate from stolen credentials to remote code execution on Strapi servers
  • Elasticsearch TLS MITM proxy -- Intercepts Elasticsearch traffic to steal queries and indexed data
  • OverlayFS container escape -- Breaks out of Docker containers to access the host filesystem
  • PostgreSQL privilege escalation -- Escalates from application-level database access to superuser
  • Raw packet capture -- Network sniffing tool for lateral movement reconnaissance
  • Automated password reset chain -- Uses stolen email credentials to trigger password resets on connected services

Exfiltration Log (8.8 MB)

  • Complete record of every beacon, every stolen credential, every exfiltrated file
  • 14 unique victim containers identified by hostname, IP, and environment

The Confirmed Victim

The exfiltration log contains production credentials for Guardarian (guardarian[.]com) -- an Estonian cryptocurrency exchange offering fiat-to-crypto conversion services. The compromised data includes:

  • PostgreSQL database credentials -- full read/write access to production databases
  • JWT and admin secrets -- allowing token forgery and admin panel access
  • API keys for CoinMarketCap and ChangeNow integrations -- the exchange's liquidity and pricing feeds
  • Three employee email addresses (alex.t@, dmitrii.s@, felipe.s@guardarian[.]com, plus roman.s@changenow[.]io) found in credential stuffing attempts
  • CORS proof-of-concept using a live Guardarian API key

The production Strapi server at 128.140.36[.]223 (Hetzner) was compromised. The attacker had database access, API key access, and was actively attempting to escalate privileges through the password reset chain and CVE-2023-22621 exploitation.

The 14 Victims

The C2 beacon log shows 14 unique containers phoning home:

  • Guardarian production server (Hetzner)
  • AWS EC2 instance (us-east region)
  • Azure CI/CD runner (pipeline compromise)
  • Multiple security sandboxes -- researchers and automated scanners that installed the package for analysis became victims themselves, leaking their own environment variables and API keys to the attacker

Victim Container Inventory

HostnameEnvironmentClassification
Guardarian production128.140.36.223 (Hetzner)Primary victim -- Estonian crypto exchange
instanceAWS 5.15.0-1084-awsGuardarian staging/dev
runnervm727z3Azure 10.1.0.217CI/CD runner (product-testing pipeline)
30cbdea844d7CentOSSecurity scanner (hscan-supplychain-dynamic) -- became a victim
ubuntu-fc-uvmFirecracker MicroVMCloud sandbox
de175f89ca12Linux 4.4.0Sandbox
18ef1a8f645aUbuntu 5.4.0-148Unknown
de1c213d1475Linux 6.12.55+Cloud instance
be7114ef13a2Linux 6.12.55+Cloud instance
dev-laptop-94b7Linux 4.4.0Sandbox (sandboxuser)
dev-laptop-d868Linux 4.4.0Sandbox (sandboxuser)
3 additionalVariousUnclassified

The sandbox infections are a particularly cruel irony. Security tools designed to detect malicious packages executed the postinstall hook in environments that contained real credentials -- CI/CD tokens, cloud API keys, internal service accounts. The attacker got intelligence about the security infrastructure monitoring them.

Infrastructure

ComponentDetail
C2 IP144[.]31[.]107[.]231 (Hetzner)
C2 port9999 (HTTP, no TLS)
Toolkit port8888 (SimpleHTTPServer, open directory)
npm accountumarbek1233 / cla4d@sharebot[.]net
Packages9 Strapi typosquats
Published2026-04-03, within 2-hour window

Immediate Actions

This is an active compromise requiring coordinated response:

  1. npm: Takedown of all 9 packages, suspension of umarbek1233 account
  2. Guardarian: Emergency credential rotation -- database passwords, JWT secrets, all API keys. Incident response engagement.
  3. Hetzner: Abuse report for 144[.]31[.]107[.]231 -- active C2 and attack toolkit hosting
  4. CERT-EE: Notification of Estonian company compromise
  5. Any organization running Strapi: Audit node_modules for the 9 package names, check for connections to 144[.]31[.]107[.]231

Indicators of Compromise

Network Indicators

  • 144[.]31[.]107[.]231 (C2 server, Hetzner)
  • Port 9999: C2 listener (HTTP)
  • Port 8888: Open directory (toolkit)

npm Packages (all by umarbek1233)

  • strapi-plugin-events@3.6.8
  • Plus 8 additional Strapi typosquats (full list in report)

Behavioral Indicators

  • postinstall script making HTTP connections to external IPs
  • Reads from /run/secrets/, /var/run/secrets/kubernetes.io/
  • Raw TCP connection to localhost:6379 (Redis)
  • Recursive search for *.pem, *.key, *.p12 files
  • Polling HTTP loop with 5-minute intervals

Share