A server security agent is the one program on a hosting box that is expected to run as root, phone home, and replace itself without asking. That is exactly why the cPGuard compromise matters more than a typical poisoned package. For roughly 70 minutes on November 2, the cPGuard update channel delivered a script that ran as root on about 2,950 customer servers, according to the vendor, OPSSHIELD. It left a hidden root shell behind and bundled up the box's most useful secrets: the shadow file, root's SSH keys, the database superuser password and everything root had typed at the prompt. Updating removes the backdoor. It does not take back anything that already left.
cPGuard is a web server security suite marketed to hosting providers and server operators, with editions for cPanel/WHM, DirectAdmin, Plesk and standalone Linux servers. If you run it anywhere, the question for this week is not only "am I patched" but "which of my secrets now belong to someone else, and where else do they open doors."
What did the cPGuard attacker get, and how did they get in?
OPSSHIELD's incident disclosure traces the entry point to its own billing portal at manage.opsshield.com, not to any customer system. The attacker registered a customer account under a false identity, ordered a trial, and was running commands on the server that handles billing and licensing by late evening on November 1. From there, a stolen access key let them upload a fake update to cPGuard's update storage in the early hours of November 2.
By the vendor's clock, probing of the portal started at 15:38 UTC on November 1, the trial signup followed at 16:34, and the attacker had code running by 22:58. The fake update was uploaded between 02:04 and 02:09 UTC, and agents installed it between 02:08 and 03:20. The vendor has closed two candidate holes in the billing application but is still working out which request the attacker actually used. It says SSH, the control panel, FTP and API tokens were not the route.
One intrusion produced two separate groups of victims, and each needs its own response:
-
About 2,950 customer servers executed the rogue build. On each one it went after
/etc/shadowand/etc/passwd, root's SSH keys andauthorized_keys,/root/.my.cnfwith the MySQL root password, root's shell history, and on some servers the S3 credentials in/root/.s3cfg. -
About 7,900 billing portal accounts lost names, company names, postal addresses, emails and bcrypt password hashes to the attacker. Card data does not appear anywhere in the disclosure.
The script did not send its loot to an attacker host. It parked the files on OPSSHIELD's licensing server under an account the attacker controlled, and the attacker collected them from there, according to the vendor's first advisory. That detail matters for detection: the stolen data went to the vendor's own infrastructure, not to an attacker-owned host, so an egress rule that trusts the vendor's servers would not have flagged it.
The two OPSSHIELD notices do not fully agree
OPSSHIELD published two notices on November 3, and the details shifted between them. Read both before you decide a server is clean.
-
Fixed version. The first advisory says cPGuard 5.88.25 or later removes the backdoor. The later disclosure says the clean release is 5.88.26, described as the build that removes the hidden root shell. Target the newest build available and confirm with
cpgcli --version. -
Exposure window. The later disclosure frames exposure as servers online between 02:08 and 03:20 UTC. The first advisory lists the bad builds as 5.88.12 to 5.88.19 and shows sample install-log entries for those versions as late as 12:46 UTC that day. Until OPSSHIELD reconciles the two, treat any server whose install log shows a 5.88.12 to 5.88.19 entry as exposed, whatever the timestamp.
-
Scale. The first advisory put the attacker's haul at roughly 32 files pulled back so far, a number it labelled preliminary and not the total of affected servers. The later disclosure gives the 2,950 server figure. BleepingComputer, which reported a spokesperson's figure of 2,946 servers, is consistent with the larger number. Plan for the larger number.
Why a security agent's update channel is the worst one to lose
Most supply-chain stories involve a library that runs with whatever rights the application has. A host security agent is different by design. It needs root to scan files and block traffic, it auto-updates so signatures stay current, and admins are trained to leave it alone. Compromise its updater and you inherit all three properties at once: root, reach, and a process nobody questions.
This is the second time in about two months that a root-running tool from the hosting stack has shipped a malicious update to its own users. In late August, a BGP hijack pushed a poisoned Virtualizor update that ran as root on hypervisor control panels. The mechanism was different (routing there, a breached billing portal here) but the lesson is the same. The update path of server-management software is a trust boundary, and most operators verify it by not thinking about it. It also echoes the poisoned security scanner that walked out of build servers with cloud keys: the tools we install to watch for intruders are attractive precisely because they are already trusted everywhere.
One more trust path deserves attention. OPSSHIELD's support access works by adding a vendor SSH public key to root's authorized_keys. The company shipped a precautionary update on November 3 to remove those keys. It has not said whether the private half of that key was exposed. Whatever the answer, a vendor key that sits in root's authorized_keys permanently is standing access, and this incident is a good moment to decide whether you want it there between support tickets.
Patching closes the hole. Rotation is the actual fix.
The fixed build deletes the backdoor and the staged files. It cannot reverse the theft. The value of what was taken differs by file, so rotate in order of how fast it can be used:
-
Plaintext first. The database superuser password stored in
/root/.my.cnf, S3 keys in/root/.s3cfg, and anything typed into root's shell history (API tokens, DigitalOcean tokens, passwords passed on a command line) need no cracking. Assume they are already in use. -
SSH keys next. Replace the private keys under
/root/.sshand remove their public halves from every machine that trusts them. OPSSHIELD's own guidance is to treat servers reachable with those keys as exposed too. On a hosting fleet that is often the backup host, the other nodes, and the jump box, so map the trust before you rotate. -
Password hashes last, but do not skip them.
/etc/shadowhashes are one-way, but weak or reused passwords fall to offline cracking. Working from a session you trust, set new passwords for root and every local account that has one, then change the same passwords anywhere they were reused. -
Portal accounts separately. Every OPSSHIELD billing customer should reset the portal password and turn on two-factor login, even if no server was online during the window.
OPSSHIELD does not recommend a routine rebuild. Its later disclosure does say that a server the attacker accessed manually should be rebuilt from a clean backup taken before November 1. The setuid-root shell the script left is the reason to take that seriously: once a root-level shell has sat on a box, a clean file listing is weak evidence. If you cannot account for what happened on the server between the install and the fix, the rebuild is cheaper than the doubt.
How do I check whether a cPGuard server was hit?
Start with cPGuard's own install log: any "Successfully updated" line naming a build between 5.88.12 and 5.88.19 means the server took an affected update. A missing or truncated log does not clear the server. Then look for the files and the persistence the script left, and search web and firewall logs for the operator address OPSSHIELD published.
grep 'Successfully updated' /opt/cpguard/logs/install_log cpgcli --version /etc/cron.d/i93677 /var/tmp/.i93677/ /var/tmp/.cpg91511_sh /var/tmp/.cpg91511/ /var/tmp/.cpg91511/loot.tgz 94.183.188.91 User-Agent: cPGuard/5.88.11 2c2acd988da531d133d0e17f245c49bf15781278f837543d18c95e8410eabd38 cpguard-v58812-i91511.sh ffd7a47f65fa1244429baa936ae94be54676b53d2f88bc39564f24e22011de8a cpguard-v58812-i91511b.sh d73919af84bf46de941db85e27fd1abc65701ef9ce6cf33bb27c5397f4a41c26 cpguard-v58812-i91511c.sh 6add0201e9a3d02de772d561d05e0da86eb1c538686753b7adcc3aedf7366f77 cpguard-v58812-i91511d.sh
The fixed build is supposed to remove the cron file and the /var/tmp paths, and OPSSHIELD asks customers to confirm they are gone and delete any that remain. Finding them absent after the update proves nothing about whether the script ran. The useful evidence lives elsewhere: the install log, outbound connections in the window, unfamiliar entries in authorized_keys, and any new setuid binaries. The disclosure also lists a gsocket backdoor and a Go implant with process-hiding tricks, but notes those came from OPSSHIELD's own internal server, not from customer machines. They are worth a search, but they are not the customer-side signature.
The last check is the one most teams forget. Watch for the stolen credentials being used after you rotate: a login with an old SSH key, a database connection from an unexpected source, S3 activity on a retired key. A theft like this tends to show up downstream weeks later, on a system that never ran cPGuard at all.