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.
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:
| Package | Published |
|---|---|
strapi-plugin-cron | 02:02 UTC |
strapi-plugin-config | 02:47 UTC |
strapi-plugin-server | 03:01 UTC |
strapi-plugin-database | 03:05 UTC |
strapi-plugin-core | 03:06 UTC |
strapi-plugin-hooks | 03:37 UTC |
strapi-plugin-monitor | 03:40 UTC |
strapi-plugin-events | 03:46 UTC |
strapi-plugin-logger | 03: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:
- Environment harvesting -- Dumps all environment variables, targeting database credentials, API keys, JWT secrets
- .env file theft -- Reads
.env,.env.local,.env.productionfrom the project root and parent directories - Strapi config exfiltration -- Parses
config/database.js,config/server.js,config/admin.jsfor connection strings and secrets - Redis key dumping -- Opens raw TCP connections to localhost:6379 and sends
KEYS *followed byGETfor each key - Docker secrets -- Reads
/run/secrets/*for Docker swarm secrets - Kubernetes tokens -- Steals
/var/run/secrets/kubernetes.io/serviceaccount/tokenand CA certificates - Private key discovery -- Recursively searches for
*.pem,*.key,*.p12,*.pfxfiles - Cryptocurrency wallet theft -- Searches for wallet files, seed phrases, and key stores
- SSH key theft -- Reads
~/.ssh/id_rsa,~/.ssh/id_ed25519, andknown_hosts - Git credential theft -- Parses
.git-credentials,.gitconfigfor stored tokens - C2 polling loop -- Opens a 5-minute polling connection to
144[.]31[.]107[.]231:9999accepting 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, plusroman.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
| Hostname | Environment | Classification |
|---|---|---|
| Guardarian production | 128.140.36.223 (Hetzner) | Primary victim -- Estonian crypto exchange |
instance | AWS 5.15.0-1084-aws | Guardarian staging/dev |
runnervm727z3 | Azure 10.1.0.217 | CI/CD runner (product-testing pipeline) |
30cbdea844d7 | CentOS | Security scanner (hscan-supplychain-dynamic) -- became a victim |
ubuntu-fc-uvm | Firecracker MicroVM | Cloud sandbox |
de175f89ca12 | Linux 4.4.0 | Sandbox |
18ef1a8f645a | Ubuntu 5.4.0-148 | Unknown |
de1c213d1475 | Linux 6.12.55+ | Cloud instance |
be7114ef13a2 | Linux 6.12.55+ | Cloud instance |
dev-laptop-94b7 | Linux 4.4.0 | Sandbox (sandboxuser) |
dev-laptop-d868 | Linux 4.4.0 | Sandbox (sandboxuser) |
| 3 additional | Various | Unclassified |
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
| Component | Detail |
|---|---|
| C2 IP | 144[.]31[.]107[.]231 (Hetzner) |
| C2 port | 9999 (HTTP, no TLS) |
| Toolkit port | 8888 (SimpleHTTPServer, open directory) |
| npm account | umarbek1233 / cla4d@sharebot[.]net |
| Packages | 9 Strapi typosquats |
| Published | 2026-04-03, within 2-hour window |
Immediate Actions
This is an active compromise requiring coordinated response:
- npm: Takedown of all 9 packages, suspension of
umarbek1233account - Guardarian: Emergency credential rotation -- database passwords, JWT secrets, all API keys. Incident response engagement.
- Hetzner: Abuse report for
144[.]31[.]107[.]231-- active C2 and attack toolkit hosting - CERT-EE: Notification of Estonian company compromise
- Any organization running Strapi: Audit
node_modulesfor the 9 package names, check for connections to144[.]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,*.p12files - Polling HTTP loop with 5-minute intervals