← All playbooks
CriticalSupply chain · 2026-11-03

OPSSHIELD cPGuard update channel hijacked: fake updates ran as root on ~2,950 Linux hosting servers

SockAnalyst · Security+ certified analyst.

What happened

On November 2, 2026 an attacker who had gained command execution on OPSSHIELD's billing and licensing server used a stolen update-storage key to push fake cPGuard updates (versions 5.88.12 through 5.88.19) through the product's trusted update channel. cPGuard is a firewall and malware-protection agent for Linux web-hosting servers. Agents that checked in during the roughly 70-minute window installed the fake update and ran its script as root. OPSSHIELD says the script collected /etc/shadow and /etc/passwd, root MySQL/MariaDB config, root shell history and root SSH files, uploaded them to OPSSHIELD's own licensing server under a compromised account, and planted a hidden setuid root shell. OPSSHIELD (disclosure published Nov 3) estimates about 2,950 servers ran the update, the attacker retrieved 32 collected files (one per customer server), and roughly 7,900 billing-portal account records were copied. It revoked the stolen key, reissued license keys and shipped clean cPGuard 5.88.25/5.88.26 that removes the backdoor.

Who's affected

Hosting providers and server admins running OPSSHIELD cPGuard on Linux (commonly cPanel, Plesk and DirectAdmin servers) whose agents installed any version from 5.88.12 through 5.88.19 on November 2, 2026, plus any system that trusts SSH keys or reuses passwords/tokens from those servers. All OPSSHIELD billing-portal users should reset their portal password.

✅ Fix / patch

  • Run cpgcli update --start on every cPGuard server and confirm cpgcli --version shows 5.88.26 or later (OPSSHIELD's advisory names 5.88.25 as the minimum corrective release).
  • Check grep 'Successfully updated' /opt/cpguard/logs/install_log for any 5.88.12-5.88.19 entry; if present, treat the server as compromised even after updating.
  • On affected servers reset root and all password-enabled local accounts from a trusted recovery environment, rotate database passwords, S3/DigitalOcean/cloud tokens and anything in root's shell history, and replace /root/.ssh keys and remove their old public keys everywhere they are trusted.
  • Confirm the hidden root shell, staging directory and scheduled task listed in IOCs are gone, then review for unfamiliar users, SSH keys, services and outbound connections; reset your OPSSHIELD billing-portal password and enable two-factor login.

🔎 Quick hunt check

Published IOCs below (file paths only; OPSSHIELD has not listed attacker IPs in the advisory reviewed). Hunt Linux hosts for the setuid shell and staging paths, cPGuard install-log entries for 5.88.12-5.88.19, reads of /etc/shadow or /root/.ssh by cPGuard child processes, and SSH logins on other hosts using keys that lived on affected servers.

MITRE ATT&CK: T1195.002, T1078, T1003.008, T1552.004, T1552.003, T1548.001, T1053.003, T1041

IOCs

  • Files / paths: cpguard-v58812-i91511.sh, /var/tmp/.cpg91511_sh, /var/tmp/.cpg91511/, /var/tmp/.cpg91511/loot.tgz, /etc/cron.d/i93677, /var/tmp/.i93677/

First four paths are from OPSSHIELD's advisory; the i93677 scheduled task and directory are from OPSSHIELD's follow-up as reported by Help Net Security. No attacker IPs, domains or hashes appear in the advisory reviewed. Absence of these files after cleanup does not prove the script never ran.

Hunt queries

Starting points built only from published indicators. Field names vary; tune before use.

index=linux (sourcetype=linux:audit OR sourcetype=syslog OR sourcetype=sysmon:linux OR sourcetype="cpguard*")
  ("cpguard-v58812-i91511.sh" OR ".cpg91511_sh" OR "/var/tmp/.cpg91511" OR "loot.tgz" OR "i93677"
   OR ("Successfully updated to 5.88.1*"))
| rex field=_raw "Successfully updated to (?<cpg_version>5\.88\.\d+)"
| eval bad_version=if(match(cpg_version,"^5\.88\.1[2-9]$"),"yes",null())
| stats count min(_time) as first max(_time) as last values(cpg_version) as versions values(bad_version) as bad by host
// Any hit = host touched by the fake cPGuard update; rotate root secrets and SSH keys
let iocs = dynamic(["cpguard-v58812-i91511.sh", ".cpg91511_sh", "/var/tmp/.cpg91511", "loot.tgz", "i93677"]);
union isfuzzy=true
  (Syslog | where TimeGenerated > ago(30d) | where SyslogMessage has_any (iocs) or SyslogMessage matches regex @"Successfully updated to 5\.88\.1[2-9]\b" | project TimeGenerated, Computer, Evidence=SyslogMessage),
  (DeviceFileEvents | where TimeGenerated > ago(30d) | where FolderPath has_any (iocs) or FileName has_any (iocs) | project TimeGenerated, Computer=DeviceName, Evidence=strcat(ActionType, " ", FolderPath, FileName)),
  (DeviceProcessEvents | where TimeGenerated > ago(30d) | where ProcessCommandLine has_any (iocs) or (InitiatingProcessFolderPath has "/opt/cpguard" and ProcessCommandLine has_any ("/etc/shadow", "/root/.ssh", ".my.cnf", ".bash_history")) | project TimeGenerated, Computer=DeviceName, Evidence=ProcessCommandLine)
| summarize Hits=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Evidence=make_set(Evidence, 10) by Computer
// Hosts with cPGuard IOC paths, bad 5.88.12-5.88.19 installs, or cPGuard reading root secrets

See all hunt queries →

⏱ CISA deadline

Not in CISA KEV as of Nov 14, 2026.

Sources

Related playbooks

Get the Weekly KEV & Hunt Pack free — plus the daily SOC brief.

Weekly email; daily brief on the website.

Subscribe free

Read the free weekly pack →