On 28 April 2026, SentinelLABS located a script through a Kubernetes-focused VirusTotal hunting rule that stood out from known cloud hack tools: the script’s first actions are to evict and delete tools associated with the TeamPCP attack group, leading us to call the toolset PCPJack. Analyzing this script led researchers to discover a comprehensive framework for cloud credential harvesting and propagation to internal and external systems.
TeamPCP stood out in early 2026 following the group’s February compromise of Aqua Security’s Trivy vulnerability scanner. The incident enabled several downstream attacks, including the compromise of LiteLLM, an open-source library that routes requests across widely used LLM providers. TeamPCP also announced a partnership with the VECT ransomware group to monetize data stolen through attacks on their cloud environment.[1]
Many of the services targeted by the PCPJack framework are similar to the early TeamPCP/PCPCat campaigns from December 2025, before the high-visibility campaigns of early 2026 brought significant attention to TeamPCP and purportedly led to changes in group membership. Analysts believe this could be a former operator who is deeply familiar with the group’s tooling.
The types of credentials collected by the framework suggest PCPJack’s targeting motivations are primarily to conduct spam campaigns and financial fraud, or to simply monetize stolen credentials for actors with these focuses. The inclusion of enterprise productivity software such as Slack and business database services broadens the focus to include extortion attacks. Notably, neither of the two toolsets we identified from the attacker’s staging server performed any cryptocurrency mining, a stark departure from typical multi-disciplinary cloud attack campaigns.
First Toolset | bootstrap.sh & Python Worms - The infection begins with bootstrap.sh, a shell script designed for Linux systems. This script serves only to set up the environment and download additional payloads. bootstrap.sh sets several key variables, including PAYLOAD_HOST, which is set to hxxps://spm-cdn-assets-dist-2026[.]s3[.]us-east-2[.]amazonaws[.]com, a legitimate Amazon Simple Storage Service (S3) resource that was likely registered by the attacker for unauthorized purposes.
Beginning of bootstrap.sh, the dropper script
The main functionality of bootstrap.sh is:
- Create /var/lib/.spm/working directory
- Check public IP against operator’s blocklist: this prevents the attacker from infecting their own infrastructure
- Find and remove processes or artifacts that match naming conventions referencing TeamPCP or PCPcat process list, services, paths, or containers
- Install Python 3.6+ via available package manager: apk, apt, dnf, pacman, yum, or zypper
- Create a Python virtual environment and install requests, cryptography, and pyarrow
- Download six Python modules from the attacker’s S3 URL in the following order: py, parser.py, lateral.py, crypto_util.py, cloud_ranges.py, cloud_scan.py
- Rename modules to their on-disk names (see the list of downloaded payloads below)
Establish persistence:
- If run as root: create sys-monitor.service, which runs py, aka worm.py, an orchestrator script
- If not root, create two crontabs: one runs every 5 minutes to check if pyis running, the other starts monitor.py if it is not running
- Launch py
- Self delete using rm -f "$0"
bootstrap.sh rival process and artifact removal
The following table itemizes the downloaded payloads:
|
S3 filename |
On-disk name |
Role |
|
worm.py |
monitor.py |
Main orchestrator |
|
parser.py |
utils.py |
Credential parsing engine |
|
lateral.py |
_lat.py |
Lateral movement |
|
crypto_util.py |
_cu.py |
Exfiltrated data encryption |
|
cloud_ranges.py |
_cr.py |
Cloud IP CIDR database |
|
cloud_scan.py |
_csc.py |
Cloud port scanner |
The logic targeting TeamPCP files stands out: each artifact has been associated with TeamPCP in public reporting, though BORING_SYSTEM is mentioned only sparingly. Sentinel initially considered that this toolset could be used by a researcher to remove TeamPCP’s infections. However, analysis of the later-stage payloads indicates otherwise. When exfiltrating system information and credentials, the PCPJack operator even collects success metrics on whether TeamPCP has been evicted from targeted environments in a “PCP replaced” field sent to the C2.
List of information sent to the attacker by monitor.py
Infection Flow - The infection begins with bootstrap.sh, which executes the orchestrator script, monitor.py (aka worm.py). The orchestrator imports a set of purpose-built modules for credential parsing (utils.py), lateral movement (_lat.py), C2 message encryption (_cu.py), cloud IP range lookups (_cr.py), and cloud scanning (_csc.py). Rather than let the modules find their own dependencies, the orchestrator injects them at runtime with shared references, ensuring all components operate with the same credential and movement handles without hardcoding inter-module imports.
The scanning module, _ csc.py, receives the lateral movement engine, the cloud range lookup function, and the credential parser all via injection from the worm. This design keeps each module independently minimal while the orchestrator alone holds the full dependency graph, making the framework harder to analyze in isolation.
No single imported file reveals the complete picture without visibility into monitor.py. Sensitive strings are stored in the source code as a hex-encoded blob instead of clear text. When a script runs, it obtains the actual value by calling function _d(), which is near the top of each Python module, against the encoded hex string containing the sensitive content.
The function decrypts it by XORing each byte against the MD5 hash of the string urllib3.poolmanager, a name chosen to look like a reference to a common Python web library. PCJack’s author encrypted the constants that would immediately identify the malware’s infrastructure.
Despite this, the actor failed to encrypt the Telegram bot token in bootstrap.sh and the credential decryption key in crypto_util.py, so the operational security awareness only goes so far.
The _d function is used to XOR decrypt sensitive constants
monitor.py | Orchestrator Script - The monitor.py script, which was hosted on the attacker’s staging server as worm.py and had persistence established by bootstrap.sh, is the main script driving the toolset. The script starts with logic designed to make it appear like a benign system-monitoring utility that collects system metrics.
While this is valuable information for the attacker, we believe it is an attempt to help the script blend in if spotted by an administrator, since the posted information also includes details about the types of systems targeted by the toolset.
Early functions in monitor.py
Local Credential Theft - On each compromised host, monitor.py executes a shell pipeline that steals:
- .envfiles and config files
- Environment variables filtered for secrets, API keys, DB & SMTP creds
- SSH private keys and targets from known_hosts, ~/.ssh/config, and bash history
- AWS IMDS credentials
- Kubernetes service account tokens
- Docker secrets (/run/secrets/)
- Cryptocurrency wallets (datfiles, Ethereum keystores, Solana keys)
A separate scan walks the local /etc, /home, /opt, /root, /srv, /var/lib , and /var/www directories looking for config or secret files, and _mgr searches through git history for deleted secrets. The results are parsed by utils.py.
Target Selection & Propagation - After credential extraction, the command checks for prior installation (/var/lib/.spm/worm.py or /var/lib/.spm/monitor.py) and, if clean, downloads and executes bootstrap.sh from the C2 payload host.
Propagation targets come from parquet files that the worm downloads directly from Common Crawl, a legitimate web-scanning archive nonprofit with a rich history of providing AI models with vast amounts of training data harvested from the web. The URL is extracted from obfuscated variables _CI and _CB.
The tool picks parquet files containing url_host_name columns, and iterates through those hostnames. Each monitor.py node gets a window of parquet files based on the date or a seed index (SPM_SEED_IDX), providing the attacker with distributed coverage without central coordination. A deduplication set, variable _sh, is stored in memory to prevent re-scanning. This list is capped at 15 million entries.
This module spreads the toolset to targets by exploiting several vulnerabilities in web technologies, including the ubiquitous React2Shell flaw:
|
CVE |
Technology |
Affected Versions |
Description |
CVSS |
|
CVE-2025-29927 |
Next.js |
< 12.3.5, 13.5.9, 14.2.25, 15.2.3 |
Middleware auth bypass via header |
8.8 |
|
CVE-2025-55182 |
React / Next.js |
React < 19.0.1; Next.js multiple lines |
Server Actions deserialization |
9 |
|
CVE-2026-1357 |
WPVivid Backup (WordPress) |
<= 0.9.123 |
Unauthenticated null-key file upload |
9.8 |
|
CVE-2025-9501 |
W3 Total Cache (WordPress) |
< 2.8.13 |
PHP injection via cached mfunc comment |
9 |
|
CVE-2025-48703 |
CentOS Web Panel (CWP) |
< 0.9.8.1205 |
Filemanager changePerm shell injection |
9.x |
Command & Control - The framework uses Telegram for C2. An infected system posts data to one channel and checks another to receive commands from a pinned message.
Most of the commands are self-explanatory. RUN downloads a module from the attacker’s payload storage, saves it as run_script.py, and executes the script. The PARQUET command assigns the node a new index to parse from the Parquet file, allowing the operator to manually override previously selected attack ranges.
Telegram commands in monitor.py
utils.py | Credential Extractor - This script handles credential extraction using regular expressions to identify and categorize stolen keys and secrets. The logic centers on a wide variety of online services, many of which pertain to bulk messaging services, cryptocurrency/FinTech, cloud or web application services.
Finance & Enterprise
|
Binance |
Bitcoin |
Coinbase |
Ethereum |
|
Gemini |
Infura |
Kraken |
KuCoin |
|
OKX |
Solana |
Stripe |
SMTP & Bulk Messaging Services
|
Amazon SES |
126[.]com |
163[.]com |
qq[.]com |
|
Gmail |
Mailchimp |
Mailgun |
Mailjet |
|
Mandrill |
Microsoft Office 365/Microsoft Outlook |
SendGrid |
Twilio |
|
Yandex |
Web & Cloud Services
|
AWS |
Access Key ID, Secret Access Key |
|
Database |
Generic database name URL, username, password |
|
Generic SMTP |
|
|
GitHub |
|
|
PHP |
API Keys and Secrets |
|
Slack |
|
|
SSH |
Private Key |
|
WordPress |
Database Password, SMTP Host Configuration, W3TC Cache Secret |
Interestingly, the actor’s regular expression matching includes credentials for FTX, a crypto exchange that went bankrupt in a high-profile case in 2022. This suggests the actor adapted the matching logic from an older tool, or that it was inserted erroneously through LLM code generation.
lateral.py | Internal Network Lateral Movement - The lateral.py or _lat.py script performs reconnaissance on the infected system and the assets it connects to, enabling internal propagation. The script runs only once and writes a lateral_done file to the working directory; if that file is found, the script exits. This is likely to improve stealth and reduce the likelihood of network security alerts.
The Kubernetes spreading logic _lk checks for a Kubernetes service account token present in pods mounted in the cluster, then uses the service account to authenticate with the Kubernetes management API to enumerate namespaces and pods in the cluster. The script runs commands against each container to:
- Extract credentials from a list of file names and paths associated with secret stores
- Harvest SSH private keys
- Query the AWS Instance Metadata Service (IMDS); this works only in environments where IMDSv2 is not strictly enforced and goes against modern default configurations and best practices
_lk also reads Kubernetes Secrets and ConfigMaps directly via the API, base64-decodes their values, which works even when pod execution is denied by role-based access controls (RBAC). Lastly, it attempts a container escape by mounting the host filesystem to a new container, enabling the attacker’s tools to interact with the host system.
The Docker propagation function _ld checks for the local Docker socket at /var/run/docker.sock, then scans the network for services running on ports 2375 or 2376. When found, the script connects to the Docker API via the management daemon, lists all running containers, and executes the same credential-harvesting script as seen in the Kubernetes routine. If connected to a remote host, the spreader will bind-mount the root filesystem of the machine running the Docker management service to the remote instance’s /host path, which creates a container escape.
When Redis is found, _rec dumps the configuration, then runs the Redis KEYS command to scan the database for key names containing secrets, passwords, tokens, and API keys, and GETs their values. For persistence, _rwc performs a Redis cron rewrite, resulting in a cron job that fires bootstrap.sh every 5 minutes as root.
lateral.py targets several other services running within the victim’s environment:
- RayML Clusters: scans port 8265, submits a Python job via the API to extract credentials and download sh
- MongoDB: scans port 27017, enumerates databases & extracts credentials
The SSH propagation module _ls searches SSH key store locations on the infected machine and parses ~/.ssh/known_hosts, ~/.ssh/config, and .bash_history for username and host combinations. It then pulls SSH keys from harvest.jsonl, a file containing credentials obtained earlier through other lateral movement techniques. These combinations are tried against any hosts running SSH. On access, it runs bootstrap.sh on the remote machine to propagate the worm.
crypto_util.py | Data Encryption
PCJack’s framework uses the crypto_util.py (aka _ cu.py, imported as a module named _crypto) script to encrypt credentials. It is called by monitor.py to exfiltrate the encrypted data before it is sent to the attacker’s Telegram channel.
The encrypt_message function:
- Generates an X25519 keypair for each message chunk
- Performs ECDH against a hardcoded attacker public key set to variable _RPK = "6d4imqQ/s/GfQCVcybdcjfTe/PMYHtZN8ZGHnEXSbRo="
- Uses the raw shared secret directly as a ChaCha20-Poly1305 key to encrypt the data
- Splits output into 2800-byte chunks: the __main__test block validates against Telegram’s 4096-character message limit
- Packs each encrypted chunk by concatenating the ephemeral public key (32 bytes), a random nonce (12 bytes), and the ciphertext, then base64-encodes the result and prepends a 🔒emoji
If the cryptography library is not installed, the function silently falls back to sending plaintext, potentially allowing credentials to be exfiltrated unencrypted during some infections. The decrypt_message function requires the private key corresponding to variable _RPK. The test keypair in __main__ (PRIVATE_KEY) is a matching test pair. If a researcher could access the attacker’s Telegram channel, there is a reasonable chance they could decrypt the stolen credentials posted there. During our testing, the Telegram API reported that the bot token was invalid, even though the malware was actively hosted and distributed during this time.
crypto_util.py main function checking credential encryption.
cloud_ranges.py | Cloud Service Provider IPs - The cloud_ranges.py (aka _cr.py) module is relatively small and simple: it collects a list of IP addresses assigned to AWS, Azure, Cloudflare, Cloudfront, Fastly, and Google Cloud Platform (GCP). The approach is to query URLs from each provider that host information about the cloud service IP ranges, which change periodically.
This allows the attacker to avoid hard-coding IP addresses into the script, which may become outdated. Once the information is retrieved, the cloud ranges are written to a file at /var/lib/.spm/_cr/ranges.json. The data is refreshed every 24 hours.
cloud_scan.py | External Propagation - The final module is cloud_scan.py (aka _csc.py), which scans external cloud services and attempts to propagate by looking for ports indicating exposed Docker, Kubernetes, MongoDB, RayML, or Redis services. When a target responds on a matching port, cloud_scan.py scans the entire /24 subnet for the responding IP and runs infection logic imported from lateral.py.
For Docker, Redis, and RayML targets, this includes installing persistence via bootstrap.sh. Docker is targeted through a privileged container with host escape, Redis through cron injection, and RayML through a weaponized job submission.
Kubernetes and MongoDB targeting results only in credential harvesting. cloud_scan.py queries unauthenticated Kubernetes API endpoints to dump secrets, and it scrapes MongoDB collections for credentials, but does not establish persistence.
Infrastructure - bootstrap.sh contains a hardcoded list of attacker infrastructure IPs excluded from targeting. Perhaps a nostalgic nod to the presumed retired cloud attack group TeamTNT, each of these IPs is a VPS server geolocated to Germany. Given the complex dynamics that could drive this attacker to focus on killing processes associated with TeamPCP activity, it is reasonable to scrutinize whether these IPs actually belong to the attacker behind PCPJack.
38.242.204[.]245
38.242.237[.]196
38.242.245[.]147
83.171.249[.]231
161.97.129[.]25
161.97.135[.]154
161.97.163[.]87
161.97.186[.]175
161.97.187[.]42
193.187.129[.]143
213.136.80[.]73
The IPs have a relatively minimal online footprint, but the available data suggests management infrastructure and potentially different malicious activity. 38.242.245[.]147 has hosted lastpass-login-help[.]com, clearly a phishing domain to harvest LastPass master credentials: a motive that aligns with this toolset's heavy credential harvesting focus.
Second Toolset | Credential Harvester & Sliver Beacons - We also identified another toolset on the attacker’s payload delivery server, unrelated to the previous one. The file check.sh is an 858-line shell script that handles everything before the beacon phones home. The script detects CPU architecture and pulls the matching Sliver binary: update.bin, update-386.bin, or update-arm.bin, depending on the system architecture.
Start of check.sh
The binary is saved locally as /var/tmp/apt-daily-upgrade to blend in with system processes. Simultaneously, check.sh sweeps IMDS endpoints, Kubernetes service accounts, Docker instances, and /proc/*/environ for credentials from 30+ services, many through a dropped Python script called extractor.py.
Targets include many services covered by the bootstrap.sh framework, with several standout new additions: Anthropic, DigitalOcean, Discord, Google API, Grafana Cloud, HashiCorp Vault, 1Password, and OpenAI keys.
Credentials harvested by extractor.py
The script then exfiltrates stolen data to hxxps://cdn[.]cloudfront-js[.]com:8443/u, a typosquatted domain mimicking CloudFront, over ports 443 or 8443. It finishes by SSH-spraying up to 10 lateral targets before self-deleting.
Sliver ELF Binaries
The update binaries are Silver C2 beacons compiled with the garble obfuscation tool, which scrambles Go type names and removes build metadata to hinder signature-based detection. Despite the obfuscation, several indicators remained across the analyzed binaries: protobuf field tags (name=BeaconID), interface method names (GetC2URI, GetBeaconInterval), and multiple Sliver-specific RPC strings, including PivotListener, PivotPeerEnvelope, WGSocksServer, and WGTCPForwarder, among others.
The binaries form a deployment set: update.bin, the 64-bit variant, targets modern Intel-based cloud infrastructure and includes CPU feature detection for Intel Sapphire Rapids, a powerful processor present in many cloud environments.
update-386.bin is a 32-bit variant that serves as a capability-identical fallback for legacy servers or 32-bit containers.
update-arm.bin is designed for ARM processors. Interestingly, each binary has a different garble seed, meaning they were compiled separately and hinder conclusive attribution to the same developer.
Conclusion - Overall, the two toolsets are well developed and indicate that the owner values writing code as a modular framework, despite some behavioral redundancies. The occasional operational security lapses were interesting, particularly their choice to encrypt everything except for Telegram credentials and their own alleged infrastructure. In the threat actor ecosystem, there is constant churn and turnover between groups: something TeamPCP alluded to before their main social media account was suspended.
TeamPCP posts on X before account suspension
Analysts have no evidence to suggest whether this toolset is associated with someone in the group or is familiar with its activities. However, the first toolset’s focus on disabling and replacing TeamPCP’s services implies a direct focus on the threat actor’s activities rather than on opportunistic cloud attacks. There are plenty of other cloud credential-harvesting campaigns that leave forensic artifacts that could be considered when performing a pre-installation cleanup early in an intrusion.
Compared to similar cloud threat actors, PCPJack stands out for its complete lack of cryptominers in all tooling we analyzed. Nearly all moderately sophisticated cloud threat campaigns deploy XMRig or similar at some point, including several of TeamPCP’s campaigns. This campaign does not, and it deliberately removes the miner functions associated with TeamPCP. Despite that, this actor has well-defined scopes for extracting cryptocurrency credentials.
Mitigations and Recommendations - The impacts of PCPJack and similar toolsets range from data exposure and extortion to financial impacts of an attacker with access to high-limit, enterprise API services.
Organizations can defend against these threats by adhering to cloud and web application security best practices. Credential management will mitigate the majority of these credential-harvesting techniques: use an enterprise-wide vault or secret management service, and ensure access to those stores is never stored in a file saved in clear text.
Ensure that authentication mechanisms comply with industry standards: require MFA for service accounts rather than relying on an API key alone. In AWS environments, ensure that IMDSV2 is enforced across all services to prevent credential theft and consider allow-listing downloads only from approved S3 resources.
Even when systems are not exposed to the internet, ensure authentication is required to manage services like Docker and Kubernetes, as these are popular lateral movement targets that can enable much deeper access through connected nodes, and restrict the scope of Kubernetes service accounts to adhere to the principle of least privilege.
Indicators of Compromise
Domains
|
cdn[.]cloudfront-js[.]com |
PCPJack check.sh C2 domain |
|
lastpass-login-help[.]com |
Domain in TLS certificate from PCPJack infrastructure IP 38.242.245.147 |
|
spm-cdn-assets-dist-2026[.]s3[.]us-east-2[.]amazonaws[.]com |
S3 subdomain hosting PCPJack tools |
IP Addresses
The following IP addresses are hardcoded into bootstrap.sh and labeled as attacker infrastructure:
|
161.97.129[.]25 |
|
161.97.135[.]154 |
|
161.97.163[.]87 |
|
161.97.186[.]175 |
|
161.97.187[.]42 |
|
193.187.129[.]143 |
|
213.136.80[.]73 |
|
38.242.204[.]245 |
|
38.242.237[.]196 |
|
38.242.245[.]147 |
|
83.171.249[.]231 |
File Hashes | SHA-1
|
005587975a483876c1fa26b64b418931019be38f |
update.bin |
|
01cebc48016395e284ac76afc1816f143ee3e7b6 |
cloud_scan.py |
|
0b86434ca5145636d745222f7e49c903ce6ef538 |
worm.py |
|
2cd2c5268e41cdece1b0506bcda3b9eba2998119 |
crypto_util.py |
|
2fab324eb0d927846c8744dc0e217beea65138e0 |
update-386.bin |
|
339cbf61c80f757085c5afb7304d69f323bdf87a |
check.sh |
|
6060da100b5cd587131a1c11a20d6e0108604744 |
update-arm.bin |
|
848ef1f638807826586802428a7ebafdc710915c |
cloud_ranges.py |
|
9c7ab48c9fdbbeecdad8433529bdab38584f0e25 |
utils.py |
|
a20a9924d92c2b06d82b79c0fe87451c650cabec |
bootstrap.sh |
|
c2dd8051d89c4efa71bd67d2df7d9b4bc3e67810 |
bootstrap.sh |
|
fed52a4bbac7b5b6ae4f76cab3eadd67e79227e3 |
lateral.py |
File System
|
/etc/systemd/system/spm-worker.service |
Persistence set by monitor.py |
|
harvest.jsonl |
File containing monitor.py harvested credentials |
|
/tmp/.origin |
Working directory path used by check.sh |
|
/var/lib/.spm |
Working directory path used by PCPJack tools |
HTTP Request Indicators
|
—-WebKitFormBoundaryx8jO2oVc6SWP3Sad |
Unique MIME multipart boundary used in PCPJack Next.js exploit request |
Strings
|
6d4imqQ/s/GfQCVcybdcjfTe/PMYHtZN8ZGHnEXSbRo= |
Attacker’s public key used to encrypt stolen credentials before exfiltration to Telegram |
This article is shared at no charge for educational and informational purposes only.
Red Sky Alliance is a Cyber Threat Analysis and Intelligence Service organization. We provide indicators of compromise information (CTI) via a notification service (RedXray) or an analysis service (CTAC). For questions, comments, or assistance, please contact the office directly at 1-844-492-7225 or feedback@redskyalliance.com
- Reporting: https://www.redskyalliance.org/
- Website: https://www.redskyalliance.com/
- LinkedIn: https://www.linkedin.com/company/64265941
Weekly Cyber Intelligence Briefings:
REDSHORTS - Weekly Cyber Intelligence Briefings
https://register.gotowebinar.com/register/5207428251321676122
[1] https://www.sentinelone.com/labs/cloud-worm-evicts-teampcp-and-steals-credentials-at-scale/
Comments