GitHub Actions Abuse Powers Distributed cPanel & WHM Exploitation
.github/workflows turn ephemeral GitHub-hosted runners into an internet-scale scanner for CVE-2026-41940, then harvest cloud, payment and source-control credentials from compromised hosting servers.Executive Summary
A threat actor gained push access to a legitimate PHP/DevOps maintainer's GitHub repositories and committed hundreds of malicious GitHub Actions workflow files into .github/workflows/. Between 2026-07-12 and 2026-07-13 Packagist auto-synchronised the poisoned development versions across all ten of that maintainer's packages: 583 malicious workflow files in total, 55 to 62 per package version. The PHP library code itself was never malicious. The execution layer is GitHub Actions.
Each workflow runs on push to any branch or on manual dispatch, spins up an ephemeral GitHub-hosted Ubuntu runner with a 350-minute timeout, detects the runner CPU architecture, downloads a matching Linux ELF payload from the actor-controlled host 43.228.157.68, writes it to /tmp/.svc, chmods it to 755 and launches it as an internet scanner. The scanner sweeps ports 80, 443, 8080, 8443 and the cPanel/WHM control-plane ports 2082, 2083, 2086 and 2087, attempts exploitation of CVE-2026-41940 (unauthenticated authentication bypass in the cPanel & WHM login flow, CVSS 3.1 base 9.8, CISA KEV since 2026-04-30, flagged for known ransomware use), and then harvests server-side secrets: AWS keys, GitHub and GitLab tokens, OpenAI and Google API credentials, Stripe keys, SendGrid and Mailgun credentials, database logins, SSH material, git remotes, environment files and configuration files.
Results leave in chunked HTTP POSTs to /api/github-results with a heartbeat to /api/github-heartbeat every 30 seconds. Fourteen recovered workflows also fired an nslookup at a unique DNSHook callback hostname, and a GitHub code search for that identifier returned roughly 6,100 matching workflow files across unrelated repositories, with broader searches on the C2 address, scanner arguments and exfiltration endpoints returning about 15,000 to 16,000 files. Those are file counts, not confirmed victims, but the reuse confirms the campaign is far larger than one maintainer.
The defensive problem splits cleanly in two. Victim side: any internet-facing cPanel or WHM server running an unpatched build is being sprayed by an unknown number of GitHub-hosted runners with clean, reputable source IPs that no threat-intel feed will block. Attacker-infrastructure side: any repository your organisation owns, and any self-hosted runner or developer workstation that executes a poisoned workflow, becomes part of the scanning fleet and leaks whatever secrets that pipeline can reach.
Defender priority: patch cPanel/WHM to the fixed build for your release tier first (the KEV due date was 2026-05-03 and is long past), then run Q9 and Q10 against every hosting server for post-exploitation evidence, and in parallel search your own GitHub org for workflow files referencing the C2 address, the DNSHook hostname, or the ipscan --exploit argument set. Source-IP blocking is not a control here: the scanning traffic originates from GitHub's own runner ranges.
Source Review & Web Hunter Notes
| Tier | Source | Key finding | Carry forward |
|---|---|---|---|
| 1 | Socket threat research (2026-07-22) | Authoritative technical writeup. Workflow structure, payload URLs, scanner argv, heartbeat/exfil endpoints, DNSHook hostname, AMD64 payload SHA-256, full IOC list, ATT&CK mapping. | Yes: primary |
| 1 | NVD: CVE-2026-41940 (API 2.0) | CVSS 3.1 9.8 / CVSS 4.0 9.3, CWE-306 missing authentication for critical function. Full affected/fixed build matrix for cPanel, WHM and WP Squared. | Yes: patch baseline |
| 1 | CISA KEV catalog (catalog release 2026-07-29) | Added 2026-04-30, remediation due 2026-05-03, knownRansomwareCampaignUse: Known. Confirms mass exploitation predates this campaign. | Yes: urgency |
| 2 | watchTowr Labs technical analysis | Root cause: CRLF injection into the cpsrvd session file via a Basic-auth password combined with an ob-less session cookie. Names the on-disk artefacts (/var/cpanel/sessions/raw/, /var/cpanel/sessions/cache/) that make victim-side triage possible. | Partial: forensic artefacts only, exploitation mechanics deliberately not reproduced here |
| 2 | The Hacker News (2026-07-23) | Independent corroboration of the 583-file count, the July 12–13 window, the C2 address and the DNSHook identifier. | Yes: corroboration |
| 2 | Cyber Security News (2026-07-23) | Reproduces the full IOC table including the /tmp/.svc drop path. Useful second attestation for every atomic indicator in this pack. | Yes: corroboration |
| 2 | SecurityOnline (2026-07) | Scale framing (6,100 to 16,000 matching files) and the panel-port restriction guidance. Notes attribution confidence is low: nobody named or charged. | Partial: scale + attribution caveat |
| n/a | cPanel vendor advisory (support.cpanel.net, 2026-04-28) | Returned HTTP 403 to both direct fetch and the fetch proxy. No snapshot saved, therefore not cited as evidence. The fixed build numbers in this pack come from the NVD affected-version matrix instead, which is snapshotted. | No: unfetchable |
Web-hunter decisions
- Every source in this pack was fetched, read and saved verbatim to
GitHubActions-cPanel-CVE-2026-41940-Hunt-sources/(7 snapshots). Every atomic indicator below traces back to at least one of them. - No injected instructions were found. All seven snapshots were scanned for text attempting to direct the research agent (fetch-this-instead, ignore-prior, claimed authorisation). Nothing matched. One source carries unrelated promotional interstitials, which is marketing noise, not an instruction, and is not disqualifying.
- The compromised maintainer is a victim, not the actor. Socket states this explicitly. The maintainer handle and the ten package names are therefore listed as exposure-audit strings for dependency review, never as detect-or-block indicators, and the account has been suspended.
- Package counts are not victim counts. The ~6,100 and ~15,000–16,000 figures are matching files in GitHub code search. Some hits are duplicate copies in one repo and some may be actor-controlled staging. This pack does not treat them as a victim population.
- Exploit mechanics are summarised, not reproduced. The watchTowr writeup contains a working exploitation walkthrough and links to a public PoC. Only the defensive residue (session-file artefacts, service names, log locations) is carried into this pack; the PoC is not linked.
Hunt Brief & Attack Chain
Attack chain
| # | Step | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Actor gains push access to a legitimate maintainer's GitHub repositories and commits 55–62 workflow YAML files per repo under .github/workflows/ | GitHub audit log, Actions run history, commit history | Native hunt N1/N2: no EDR visibility on github.com |
| 2 | Packagist auto-synchronises the poisoned dev-* branch versions across all ten packages (2026-07-12 → 07-13) | Composer lockfiles, dependency inventory | Native hunt N5: dependency exposure audit |
| 3 | A push (any branch) or a manual workflow_dispatch launches an ephemeral ubuntu-latest runner, timeout-minutes: 350 | GitHub Actions run log; on self-hosted runners, ProcessRollup2 | Q11: runner process anomaly |
| 4 | Runner detects CPU architecture and pulls the matching ELF from /api/dl/{386,amd64,arm,arm64} using curl with a wget fallback, writing to /tmp/.svc, then chmod 755 | ProcessRollup2, ELFFileWritten, NetworkConnectIP4 | Q1, Q3, Q4, Q6, Q7 |
| 5 | Scanner runs: ipscan --source random,all --exploit CVE-2026-41940 --git --envdump --ports 80,443,8080,8443,2082,2083,2086,2087 --git-workers 20 --count 0 --no-reverse | ProcessRollup2 CommandLine, NetworkConnectIP4 | Q5, Q8 |
| 6 | Fourteen workflow variants also nslookup a unique DNSHook hostname to confirm execution out-of-band | DnsRequest | Q2 |
| 7 | Internet-facing cPanel/WHM servers are exploited through the CVE-2026-41940 authentication bypass in the login flow | cPanel login_log, /var/cpanel/sessions/, web/panel access logs | Native hunts N3/N4; Q9 for the process residue |
| 8 | On the compromised server the payload harvests AWS keys, GitHub/GitLab tokens, OpenAI and Google API keys, Stripe keys, SendGrid/Mailgun creds, DB credentials, SSH keys, git remotes, env and config files | ProcessRollup2, FileOpenInfo | Q9, Q10 |
| 9 | Exfiltration: chunked HTTP POST to /api/github-results (up to 2,000 new lines live, 5,000 in the final stage, offset-tracked) plus a 30-second heartbeat to /api/github-heartbeat | NetworkConnectIP4, ProcessRollup2 CommandLine | Q1, Q6 |
| 10 | Stolen source-control tokens feed further repository compromise, restarting the loop | GitHub audit log, secret-scanning alerts | Native hunt N2 |
Hunt hypotheses (ordered by fidelity)
| # | Hypothesis | ATT&CK | Falcon events | Expected FP | Conf |
|---|---|---|---|---|---|
| H1 | A managed host has contacted the campaign C2 at 43.228.157.68 on any port, in either direction of the kill chain (payload pull or result exfil). | T1105, T1041 | NetworkConnectIP4 | None credible. This is a single dedicated actor host. | High |
| H2 | A managed host has resolved the unique DNSHook callback hostname, proving a poisoned workflow executed with that host's resolver. | T1071.004 | DnsRequest | Security researchers replaying the sample; DNS logging appliances doing passive resolution. | High |
| H3 | The known AMD64 scanner ELF has executed or been written on a managed Linux host. | T1105 | ProcessRollup2, ELFFileWritten | None. Hash match is exact. | High |
| H4 | A process has run with the scanner's distinctive argument set (ipscan plus --exploit / --envdump / --git-workers / --no-reverse), even if the binary name or hash rotated. | T1595.002, T1190 | ProcessRollup2 | Sanctioned internal scanners using a coincidentally similar flag; rare. | High |
| H5 | A dot-prefixed ELF has been dropped into /tmp and executed: the drop pattern, independent of the specific name .svc. | T1105, T1059.004 | ProcessRollup2, ELFFileWritten | Some packaging and build tooling stages hidden temp binaries; container image builds. | Medium-high |
| H6 | A host has produced outbound fan-out to the cPanel/WHM control-plane ports 2082/2083/2086/2087 across many distinct destinations, i.e. it is scanning, not administering. | T1595.002, T1496.002 | NetworkConnectIP4 | Hosting-provider monitoring, backup agents, migration tooling, legitimate multi-server admin jumpboxes. | Medium-high |
| H7 | A cPanel/WHM service process has spawned a shell, interpreter or download tool: the post-exploitation residue of the authentication bypass. | T1190, T1059.004 | ProcessRollup2 | cPanel hooks, custom WHM plugins, cpanel-provided maintenance scripts. | High |
| H8 | Credential material (~/.aws/credentials, .env, .git-credentials, id_rsa, .npmrc) is being enumerated or read en masse on a hosting server. | T1552.001, T1005, T1119 | ProcessRollup2, FileOpenInfo | Backup jobs, config-management agents, legitimate deploy pipelines. Noisy by design. | Medium |
| H9 | A self-hosted CI runner or developer workstation has executed network tooling or a /tmp binary as a child of the runner process. | T1195.001, T1584.006 | ProcessRollup2 | Legitimate CI jobs do download things. Requires per-org baselining. | Medium |
| H10 | Workflow files under .github/workflows are being written or modified by a shell rather than by a developer's editor or an approved automation identity. | T1195.001 | ProcessRollup2 | High. Normal development touches these paths constantly. | Medium-low |
Consolidated IOC Table
| Type | Value | Conf | Action | Context | Expiry |
|---|---|---|---|---|---|
| IPv4 | 43.228.157.68 | High | detect | C2: payload delivery, heartbeat and exfiltration, served on tcp/80 | 2027-01-31 |
| URL | http://43.228.157.68:80/api/dl/386 | High | detect | 32-bit x86 Linux payload | 2027-01-31 |
| URL | http://43.228.157.68:80/api/dl/amd64 | High | detect | 64-bit x86 Linux payload | 2027-01-31 |
| URL | http://43.228.157.68:80/api/dl/arm | High | detect | 32-bit ARM Linux payload | 2027-01-31 |
| URL | http://43.228.157.68:80/api/dl/arm64 | High | detect | 64-bit ARM Linux payload | 2027-01-31 |
| URL | http://43.228.157.68:80/api/github-heartbeat | High | detect | Execution telemetry, posted every 30 seconds with repo name and last log line | 2027-01-31 |
| URL | http://43.228.157.68:80/api/github-results | High | detect | Chunked, offset-tracked exfiltration of harvested credentials | 2027-01-31 |
| Domain | f5b0b742-240a-4811-8a5b-b0ba6060685d.dnshook.site | High | detect | DNSHook out-of-band execution callback; the campaign's highest-fidelity pivot | 2027-01-31 |
| SHA-256 | 22f721fd3a81d2e27cbf90a122bb977f630c50b79daa98350f0e57b04dfa81f1 | High | detect | Recovered AMD64 Linux scanning and exploitation payload | 2027-01-31 |
| Filename / path | /tmp/.svc | High | hunt | Drop path on the runner. Trivially renameable, so hunt the pattern (Q4), do not block the literal string | 2026-10-31 |
| Command signature | ipscan --source random,all --exploit CVE-2026-41940 --git --envdump --no-reverse | High | hunt | Scanner argv. Survives binary rename and hash rotation | 2026-10-31 |
| URI path fragment | /api/github-results, /api/github-heartbeat, /api/dl/ | High | hunt | Endpoint layout. Survives an IP change if the actor redeploys the same panel | 2026-10-31 |
| Ports | 2082, 2083, 2086, 2087 | Medium | enrich | cPanel/WHM control plane. Legitimate service ports, never block outright, use for fan-out scoring (Q8) | 2026-08-31 |
| Maintainer handle | dinushchathurya | High | pivot | Victim identifier, not attribution. Use only for Composer dependency exposure audit | 2026-08-31 |
Perishability: the C2 host and the DNSHook hostname are the only truly atomic indicators, and both are rotatable in minutes. The durable coverage in this pack is behavioural: Q4 (dot-prefixed /tmp ELF), Q5 (scanner argv), Q8 (panel-port fan-out) and Q9 (cpsrvd child process) all keep working after the actor moves infrastructure. Deploy those as the standing detections and treat the IP, URL and hash rows as a 90-day sweep.
Affected Surface & Telemetry Matrix
| Surface | Exposure | Required telemetry | Priority | Gap risk |
|---|---|---|---|---|
| Internet-facing cPanel / WHM servers (Linux) | Direct, unauthenticated exploitation of CVE-2026-41940; full credential harvest on success | Falcon Linux sensor: ProcessRollup2, NetworkConnectIP4, ELFFileWritten; cPanel login_log; panel access logs | Critical | Low, if the sensor is deployed. Many hosting fleets run cPanel boxes without EDR, which is the real gap |
| Self-hosted GitHub Actions runners (Linux) | Executes the poisoned workflow with whatever secrets and network reach the runner has | ProcessRollup2, NetworkConnectIP4, DnsRequest, ELFFileWritten | High | Low |
| GitHub-hosted runners (ubuntu-latest) | Where the campaign actually ran. Ephemeral, third-party compute | None. No sensor placement is possible | High | Total blind spot: must be covered by GitHub Actions run logs and org policy, not EDR |
| Developer workstations (Linux / macOS) | Cloning a poisoned repo is safe; only push or manual dispatch triggers execution. Local risk is a developer running the workflow by hand | ProcessRollup2, DnsRequest | Medium | Medium: macOS coverage of the ELF payload is not applicable, but the curl/DNS telemetry still lands |
| PHP / Composer projects | Ten Packagist packages carried the workflows in vendor/. Not executed by Composer install, but they are contaminated artefacts in your lockfile | SCA tooling, composer.lock inventory | Medium | High: no EDR signal at all; needs a dependency audit |
| Cloud tenants reachable from the harvested keys | Second-stage risk: AWS keys, Stripe keys, SendGrid/Mailgun creds, OpenAI and Google API keys stolen from compromised servers | CloudTrail / provider audit logs, Falcon Cloud Security if licensed | High | Medium: depends on whether server-resident keys are inventoried at all |
| Source-control tenants | Stolen GitHub/GitLab tokens enable the next round of repository compromise | GitHub audit log, secret-scanning alerts, OAuth app inventory | High | Medium |
| Windows estate | Effectively none. The payload is Linux-only ELF for x86/x86-64/ARM/ARM64 | n/a | Low | n/a, do not spend Windows detection budget here beyond the DNS and C2 IP sweeps, which are platform-agnostic |
ATT&CK Mapping
| Tactic | Technique | Observed behaviour | Query / control |
|---|---|---|---|
| Resource Development | T1584.006: Compromise Infrastructure: Web Services | Compromised GitHub repositories and GitHub-hosted runners used as disposable attack infrastructure | N1, N2 · Hardening H-N2 |
| Initial Access | T1195.001: Supply Chain Compromise: Software Dependencies and Development Tools | 583 malicious workflow files pushed into a legitimate maintainer's repos; auto-synced to Packagist | Q12, N5 · Hardening H-I1 |
| Initial Access | T1190: Exploit Public-Facing Application | CVE-2026-41940 authentication bypass against internet-facing cPanel and WHM login flows | Q9, N3, N4 · Hardening H-I2 |
| Execution | T1059.004: Command and Scripting Interpreter: Unix Shell | Workflow run: steps execute curl/wget, chmod and the scanner under bash on an Ubuntu runner | Q5, Q7, Q11 |
| Command and Control | T1105: Ingress Tool Transfer | Architecture-matched ELF pulled from /api/dl/{386,amd64,arm,arm64} to /tmp/.svc, then chmod 755 | Q1, Q3, Q4, Q6, Q7 |
| Discovery | T1082: System Information Discovery | Runner CPU architecture detection to select the correct payload build | Q11 (partial) |
| Reconnaissance | T1595.002: Active Scanning: Vulnerability Scanning | ipscan --source random,all --ports 80,443,8080,8443,2082,2083,2086,2087 across the public internet | Q5, Q8 |
| Impact | T1496.002: Resource Hijacking: Bandwidth Hijacking | Free GitHub Actions compute and egress consumed as scanning capacity, at the repo owner's expense | N1 · Hardening H-N1 |
| Credential Access | T1552.001: Unsecured Credentials: Credentials in Files | Harvests AWS keys, GitHub/GitLab tokens, OpenAI and Google API keys, Stripe keys, SendGrid/Mailgun creds, DB creds, SSH material | Q10 · Hardening H-C1, H-C2 |
| Collection | T1005: Data from Local System | Environment files, configuration files and git remotes read off the compromised server | Q10 |
| Collection | T1119: Automated Collection | --envdump and --git with 20 git workers automate the harvest at scan speed | Q5 |
| Collection | T1074.001: Data Staged: Local Data Staging | Results written to local output files on the runner, then read incrementally by the exfil loop | Q6 |
| Command and Control | T1071.001: Application Layer Protocol: Web Protocols | Plain HTTP POST to the C2 API on tcp/80 | Q1, Q6 |
| Command and Control | T1071.004: Application Layer Protocol: DNS | nslookup of a unique DNSHook hostname as an out-of-band execution confirmation channel | Q2 |
| Exfiltration | T1020: Automated Exfiltration | Offset-tracked chunked upload: up to 2,000 new lines per live request, 5,000 in the final stage, running even when the scanner fails | Q1, Q6 |
| Exfiltration | T1041: Exfiltration Over C2 Channel | Everything leaves over the same HTTP API that delivered the payload | Q1, Q6 |
Native Audit-Log Hunts (non-CQL)
The most important part of this campaign happened on GitHub-hosted compute where no EDR sensor can exist. These checks are not optional extras: for step 3 of the attack chain they are the only coverage.
N1 · GitHub Actions run-history review (org-wide)
- List every workflow run in the last 90 days with an unusually long duration. The campaign's workflows set
timeout-minutes: 350, so a scanning job looks like a job that never finishes. - Flag any workflow triggered on
pushtobranches: ['**']combined withworkflow_dispatch, particularly in repositories with no history of CI. - Flag repositories with an anomalous count of workflow files. Legitimate projects rarely carry 55 to 62 of them.
# GitHub CLI - inventory workflow files and long-running runs across the org
gh repo list YOUR_ORG --limit 1000 --json nameWithOwner --jq '.[].nameWithOwner' | while read -r R; do
N=$(gh api "repos/$R/actions/workflows" --jq '.total_count' 2>/dev/null)
[ -n "$N" ] && [ "$N" -gt 10 ] && echo "REVIEW $R workflows=$N"
done
# Runs that ran longer than an hour in the last 90 days
gh api "repos/YOUR_ORG/YOUR_REPO/actions/runs?per_page=100" \
--jq '.workflow_runs[] | select(.conclusion != null)
| {name, created_at, updated_at, event, actor:.actor.login, url:.html_url}'
N2 · GitHub code search across your own org
Search your organisation's repositories for the campaign's own markers. This is the same pivot the researchers used, pointed inward.
# Search YOUR org only. Each of these is a campaign marker, not a generic string. gh search code --owner YOUR_ORG "43.228.157.68" gh search code --owner YOUR_ORG "dnshook.site" gh search code --owner YOUR_ORG "api/github-heartbeat" gh search code --owner YOUR_ORG "api/github-results" gh search code --owner YOUR_ORG "ipscan --exploit" gh search code --owner YOUR_ORG "GOMEMLIMIT" --filename "*.yml" # Local equivalent across every cloned repo on a build host grep -rIl --include='*.yml' --include='*.yaml' \ -e '43.228.157.68' -e 'dnshook' -e 'github-heartbeat' -e 'github-results' -e 'ipscan' \ /path/to/repos/*/.github/workflows/ 2>/dev/null
N3 · cPanel / WHM login log review (victim side)
- Review
/usr/local/cpanel/logs/login_logfor bursts of failed logins immediately followed by an apparently successful session from the same source. The bypass mints a pre-auth session by failing a login first, so a failure that is promptly followed by authenticated API activity from the same IP is the shape to look for. - Review
/usr/local/cpanel/logs/access_logfor requests carrying acpsesstoken that has no matching successful authentication event. - Correlate against WHM's own
Manage External Authenticationsand active-session list for sessions you cannot account for.
N4 · cPanel session-file artefact sweep (victim side)
The vulnerability writes attacker-controlled records into the on-disk session store. Two directories hold the evidence: /var/cpanel/sessions/raw/ (line-oriented key=value) and /var/cpanel/sessions/cache/ (JSON). A raw session file whose pass= record is followed by additional top-level records such as hasroot, tfa_verified or successful_internal_auth_with_timestamp is a forged session.
# Read-only triage. Run as root on a suspect cPanel/WHM host.
# Session files containing privilege records injected after the pass= line.
for F in /var/cpanel/sessions/raw/*; do
awk '/^pass=/{seen=1;next} seen && /^(hasroot|tfa_verified|successful_internal_auth_with_timestamp)=/{print FILENAME": "$0}' "$F"
done
# Preserve before you clean. Hash and copy, do not edit in place.
tar -czf /root/cpanel-session-evidence-$(date +%F).tar.gz \
/var/cpanel/sessions/raw /var/cpanel/sessions/cache \
/usr/local/cpanel/logs/login_log /usr/local/cpanel/logs/access_log
# Confirm the installed build against the fixed versions in section 12.
/usr/local/cpanel/cpanel -V
cPanel also publishes an IOC detection script for servers that were exposed while unpatched. Run the vendor's current script from the official support article before declaring a host clean.
N5 · Composer / Packagist dependency exposure audit
Installing an affected package does not execute the workflows: Composer places them in vendor/ where GitHub ignores nested workflow files. But a contaminated artefact in your lockfile is still an artefact you need to replace, and it means your dependency review missed a compromised maintainer.
# Any of the ten affected packages present in a lockfile grep -rIl --include='composer.lock' -e 'dinushchathurya/' /path/to/projects/ 2>/dev/null # Any dependency pinned to a branch-based dev version rather than a stable release grep -rIn --include='composer.json' -e '"dev-' -e '@dev' /path/to/projects/ 2>/dev/null # Nested workflow files inside vendor/ - contamination marker, not execution find /path/to/projects -path '*/vendor/*/.github/workflows/*' -name '*.yml' 2>/dev/null
CrowdStrike LogScale CQL Hunt Queries
No query below carries an in-query time filter. Set the lookback with the console's time picker; each card records the intended window as a // LOOKBACK: comment. Start at 90 days for the atomic sweeps (Q1–Q3) and 7 days for the behavioural detections.
Looks for: any outbound connection from any managed host to the campaign C2. The same host serves payload delivery, the 30-second heartbeat and the chunked result upload, so a single hit covers three chain steps. Highest-value single query in the pack, run it first, at 90 days.
// HUNT: Outbound connection to GitHub-Actions-abuse campaign C2 // MITRE: T1105 | CONF: high FP: low COST: low // REQUIRES: NetworkConnectIP4 (Linux, Windows, macOS) // LOOKBACK: 90d (set in the console time picker) // FALSE POSITIVES: none credible - dedicated actor-controlled host #event_simpleName=NetworkConnectIP4 | RemoteAddressIP4="43.228.157.68" | table([@timestamp, aid, ComputerName, UserName, ContextBaseFileName, LocalAddressIP4, RemoteAddressIP4, RemotePort]) | sort(@timestamp, order=desc)
Looks for: resolution of the campaign's unique DNSHook hostname, or of any dnshook.site subdomain. Fourteen recovered workflows fire this nslookup to confirm execution out-of-band. Matching the parent domain rather than only the exact FQDN keeps the query alive if the actor mints a new callback GUID.
// HUNT: DNSHook callback resolution (campaign execution confirmation) // MITRE: T1071.004 | CONF: high FP: low COST: low // REQUIRES: DnsRequest // LOOKBACK: 90d (set in the console time picker) // FALSE POSITIVES: security researchers detonating the sample; a passive-DNS // appliance replaying queries. Both are identifiable by the requesting host. #event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/ | DomainName=/dnshook\.site$/i | table([@timestamp, aid, ComputerName, UserName, DomainName, ContextBaseFileName, ContextProcessId_decimal]) | sort(@timestamp, order=desc)
Looks for: the recovered AMD64 scanner ELF, either executing or landing on disk. Only the amd64 build was recovered and published, so the 386, arm and arm64 variants have no published hash: Q4 through Q7 are what cover those.
// HUNT: Campaign scanner payload by SHA-256 (amd64 build) // MITRE: T1105 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 / ELFFileWritten // LOOKBACK: 90d (set in the console time picker) // FALSE POSITIVES: none - exact hash match #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|ELFFileWritten)$/ | SHA256HashData="22f721fd3a81d2e27cbf90a122bb977f630c50b79daa98350f0e57b04dfa81f1" | table([@timestamp, aid, ComputerName, UserName, #event_simpleName, FileName, ImageFileName, TargetFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: the drop pattern rather than the name. The payload lands at /tmp/.svc, but a dot-prefixed short-named executable running out of a world-writable temp directory is the durable signal and survives a rename. FP: container image builds and some packaging tooling stage hidden helpers in /tmp; exclude your known build-agent images and any .X11-unix-style socket paths by name before promoting.
// HUNT: Hidden dot-prefixed ELF executed from a world-writable temp dir // MITRE: T1105, T1059.004 | CONF: high FP: medium COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 (Linux) // LOOKBACK: 7d (set in the console time picker) // FALSE POSITIVES: build/packaging tooling staging hidden helper binaries in /tmp // TUNING: exclude your build-agent host group by ComputerName, and exclude any // approved helper basenames by adding e.g. | FileName!=/^\.(build|cache)-/i #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | ImageFileName=*/tmp/.* | FileName=/^\.[A-Za-z0-9_-]{1,12}$/ | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, ImageFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: the scanner's distinctive command line, independent of binary name or hash. Requiring both the ipscan subcommand and at least one of the campaign-specific flags keeps this precise. This is the single best behavioural detection in the pack: promote it to a Custom IOA.
// HUNT: Distributed scanner invoked with the campaign's argument set // MITRE: T1595.002, T1190, T1119 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // LOOKBACK: 30d (set in the console time picker) // FALSE POSITIVES: a sanctioned internal scanner using a coincidentally similar // flag set. Verify the binary path and hash before closing as benign. #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/\bipscan\b/i | CommandLine=/--exploit|--envdump|--git-workers|--no-reverse|--source[ =]random/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, ImageFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: the campaign's endpoint layout in any process command line: the architecture download paths, the heartbeat, or the chunked-results upload. Because the workflows call these through curl with an explicit URL, the path survives an IP rotation as long as the actor redeploys the same panel software.
// HUNT: Campaign C2 API paths referenced on a command line // MITRE: T1105, T1020, T1041 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // LOOKBACK: 30d (set in the console time picker) // FALSE POSITIVES: none observed - these paths are campaign-specific #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/\x2fapi\x2f(github-heartbeat|github-results|dl\x2f(386|amd64|arm64|arm))/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: the generic tradecraft that survives every IOC rotation in this campaign: a download tool fetching from a raw IP address (no hostname, no TLS) and writing into a temp directory. Socket's own defensive guidance calls out exactly this: alert on payload downloads from raw IP addresses. FP: internal artifact mirrors and air-gapped repos are frequently addressed by IP; baseline your own mirror addresses out first.
// HUNT: Download tool fetching from a raw IPv4 over plaintext HTTP into temp // MITRE: T1105 | CONF: medium FP: medium COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // LOOKBACK: 7d (set in the console time picker) // FALSE POSITIVES: internal package mirrors and artifact stores addressed by IP; // provisioning scripts that pull from a bootstrap host // TUNING: exclude your own mirrors by adding a negative match on their octets, // e.g. | CommandLine!=/http:\x2f\x2f10\.20\.30\./, and exclude sanctioned // provisioning parents by ParentBaseFileName (cloud-init, ansible, chef-client) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(curl|wget)$/i | CommandLine=/http:\x2f\x2f\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ | CommandLine=/-o[ =]\x2ftmp\x2f|-O[ =]\x2ftmp\x2f|\x2ftmp\x2f\./ | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: a managed host behaving like a scanner rather than an administrator: many distinct destination IPs on 2082/2083/2086/2087. Administering hosting servers touches a handful of known addresses; scanning touches hundreds. Threshold is deliberately conservative; lower it after you see your own baseline. FP: hosting-provider monitoring, migration tooling and backup agents legitimately fan out on these ports, exclude those specific hosts, not the ports.
// HUNT: Panel-port fan-out consistent with mass cPanel/WHM scanning // MITRE: T1595.002, T1496.002 | CONF: high FP: medium COST: medium // REQUIRES: NetworkConnectIP4 // LOOKBACK: 24h (set in the console time picker) // FALSE POSITIVES: hosting-provider monitoring, server-migration tooling, // backup agents, and multi-tenant admin jumpboxes // TUNING: exclude sanctioned management hosts by ComputerName before promoting, // and raise the threshold until your own admin jumpboxes fall below it #event_simpleName=NetworkConnectIP4 | in(field=RemotePort, values=["2082", "2083", "2086", "2087"]) | groupBy([aid, ComputerName], function=[count(field=RemoteAddressIP4, distinct=true, as=distinct_targets), count(as=total_conns)]) | distinct_targets > 25 | sort(distinct_targets, order=desc)
Looks for: the victim-side post-exploitation residue. CVE-2026-41940 gives the attacker an authenticated control-plane session; anything they do next runs as a child of the cPanel daemons. A panel daemon spawning bash, perl, python, curl, wget or a network tool is the highest-fidelity victim-side signal available in EDR, and it does not depend on any campaign IOC.
// HUNT: cPanel/WHM daemon spawning a shell, interpreter or download tool // MITRE: T1190, T1059.004 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 (Linux, cPanel hosts) // LOOKBACK: 30d (set in the console time picker) // FALSE POSITIVES: cPanel hooks and custom WHM plugins legitimately shell out; // confirm the child command line before escalating #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | ParentBaseFileName=/^(cpsrvd|cpsrvd-ssl|whostmgrd|cpanellogd|queueprocd|cpdavd|dovecot)$/i | FileName=/^(sh|bash|dash|zsh|ksh|perl|python|python3|php|curl|wget|nc|ncat|socat|chmod|chattr|base64|xxd)$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, ImageFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: the credential-harvest stage: command lines touching the exact secret stores the scanner targets. Run this scoped to hosting servers only, and correlate against Q9 hits from the same host and hour rather than triaging it standalone. FP: very high: backup jobs, config-management agents and deploy pipelines read these paths constantly. This is a correlation input, not an alert.
// HUNT: Bulk read/enumeration of cloud, git and SSH credential material // MITRE: T1552.001, T1005, T1119 | CONF: medium FP: high COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 (Linux) // LOOKBACK: 7d (set in the console time picker) // FALSE POSITIVES: backup agents, ansible/chef/puppet runs, CI deploy steps, // and administrators legitimately reading their own dotfiles // TUNING: exclude backup and config-management parents first -- // | ParentBaseFileName!=/^(ansible|ansible-playbook|chef-client|puppet|restic|borg|rsync|duplicity)$/i // then require 2 or more distinct credential paths per host per hour before triage #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | event_platform=Lin | FileName=/^(cat|grep|egrep|find|tar|zip|awk|sed|head|xargs|strings)$/i | CommandLine=/\.aws\x2fcredentials|\.git-credentials|\.ssh\x2fid_(rsa|ed25519|ecdsa)|\.env\b|\.npmrc|\.pypirc|\.docker\x2fconfig\.json|\.netrc/i | groupBy([aid, ComputerName, UserName, ParentBaseFileName], function=[count(as=hits), collect([CommandLine])]) | sort(hits, order=desc)
Looks for: the campaign's execution shape on the one slice of CI you can instrument. GitHub-hosted runners are invisible to EDR, but a self-hosted runner is your host, and a poisoned workflow scheduled onto it behaves identically. FP: legitimate CI jobs download dependencies all day; the discriminator is a temp-directory binary or a raw-IP fetch, not the download itself.
// HUNT: Self-hosted Actions runner spawning download tooling or a /tmp binary // MITRE: T1195.001, T1584.006, T1059.004 | CONF: medium FP: medium COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 on self-hosted runners // LOOKBACK: 7d (set in the console time picker) // FALSE POSITIVES: normal CI dependency fetches and toolchain installers // TUNING: baseline your pipelines first, then exclude approved build steps by // ParentBaseFileName, and keep only rows where ImageFileName sits under /tmp // or the command line targets a raw IPv4 host #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(Runner\.Worker|Runner\.Listener|run-helper\.sh|runsvc\.sh|actions-runner)$/i | FileName=/^(curl|wget|nc|ncat|socat|chmod|nslookup|dig|host)$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, ImageFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: bulk creation or rewriting of workflow YAML from a shell or scripting interpreter rather than an editor or IDE: the local footprint of the actor's 55-to-62-files-per-repo push, if it happened on a host you instrument. FP: high by nature. Developers and scaffolding tools legitimately generate workflow files; the signal is volume from a non-interactive parent, so triage on the grouped count, not on individual rows.
// HUNT: Bulk workflow-file manipulation from a shell or scripting interpreter // MITRE: T1195.001 | CONF: medium FP: high COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // LOOKBACK: 30d (set in the console time picker) // FALSE POSITIVES: developers editing CI, cookiecutter/yeoman-style scaffolding, // IaC generators, and repo-templating scripts // TUNING: exclude interactive developer sessions -- drop rows whose // ParentBaseFileName is an IDE or terminal, and only review hosts where the // grouped count exceeds 20 workflow-file operations in one hour #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/\.github\x2fworkflows/i | FileName=/^(sh|bash|dash|zsh|perl|python|python3|curl|wget|cp|mv|tee|git)$/i | groupBy([aid, ComputerName, UserName, ParentBaseFileName], function=[count(as=ops), collect([CommandLine])]) | sort(ops, order=desc)
CQL quality-review notes
- All twelve queries use documented Falcon data-model fields.
event_platformis a standard sensor field but is tenant-visible only where Linux sensors are deployed: ⚠ validate in tenant that Linux telemetry is actually flowing before trusting an empty result from Q4, Q9, Q10 or Q12. - No query carries an in-query time filter. An expression on the right of a comparison does not parse in CQL, so the window is set in the console and recorded as a
// LOOKBACK:comment on each card. - Forward slashes inside every regex literal are written as
\x2fso no regex closes early. Basenames are matched throughFileName, never through a path regex. - Every card rated FP medium or higher carries a
// TUNING:line inside the query block, so the exclusion travels with the query when an analyst copies it. - Q3 covers only the amd64 build. The 386, arm and arm64 payloads were distributed but no hashes were published for them, so they are covered behaviourally by Q4–Q7 rather than by a fabricated hash.
CrowdStrike Custom IOA Recommendations
Three queries are clean enough to promote to blocking or high-severity Custom IOAs. The rest stay Investigate-only.
| IOA | Source | Platform / type | Pattern | Action | Exclusions |
|---|---|---|---|---|---|
| IOA-1: Distributed scanner argument set | Q5 | Linux · Process Creation | Command line matches .*\bipscan\b.* AND matches .*(--exploit|--envdump|--git-workers|--no-reverse).* | Block execution · Critical | None recommended. If you operate a sanctioned scanner with a colliding flag set, exclude it by full image path, never by flag. |
| IOA-2: cPanel daemon spawns a shell | Q9 | Linux · Process Creation | Parent image ends with cpsrvd, cpsrvd-ssl, whostmgrd or cpdavd AND child image basename matches a shell, interpreter or download tool | Detect · High (move to Block after two clean weeks) | Exclude specific cPanel hook scripts by full child path after you have baselined them. Do not exclude by parent: that removes the whole detection. |
| IOA-3: Hidden temp binary execution | Q4 | Linux · Process Creation | Image path matches .*\x2ftmp\x2f\..{1,12}$ AND the file is executable | Detect · High | Exclude your build-agent host group; exclude approved helper basenames individually. |
Custom IOC entries (Falcon IOC Management, not IOA)
43.228.157.68: Block, Critical. Single-purpose actor host; there is no legitimate reason for any managed asset to reach it.f5b0b742-240a-4811-8a5b-b0ba6060685d.dnshook.site: Block, Critical. Detonation-only hostname.22f721fd3a81d2e27cbf90a122bb977f630c50b79daa98350f0e57b04dfa81f1: Prevent, Critical.
Investigate-only (no alert email)
Q7, Q8, Q10, Q11 and Q12 are hunt queries. They earn their keep as scheduled searches feeding a weekly review queue and as correlation inputs behind IOA-1 through IOA-3, not as pageable alerts. Q1, Q2, Q3 and Q6 are covered by the IOC entries above and need no separate IOA.
Alert package: IOA-1 (the one that should page)
- Scheduled Search name:
DETECT: Distributed cPanel Scanner Execution (GitHub Actions Abuse) - Description: Fires when a process executes with the internet-scanner argument set used by the GitHub Actions abuse campaign to mass-exploit CVE-2026-41940 in cPanel and WHM. The host is either a compromised CI runner or an asset the actor is staging from.
- Email subject:
[CRITICAL] Detection: distributed cPanel/WHM scanner executing on HOST-01 - Body: what fired (scanner argv match), what it means (this host is running mass-exploitation tooling against the public internet and harvesting credentials from anything it lands on), immediate actions (network-contain the host, capture the binary and its hash, pull the parent process tree, identify the triggering workflow or job), escalation (CI/platform owner plus incident commander, this is an outbound-attack event with third-party liability, not only an infection).
Triage checklist (L1, in order)
- Confirm the process is not a sanctioned internal scanner: check full image path and SHA-256 against your approved-tooling list.
- Network-contain the host in Falcon before further analysis. The harm is ongoing and outbound.
- Run Q1 scoped to that
aidfor C2 contact; run Q2 for the DNSHook callback. Either confirms campaign attribution. - Pull the parent process tree. If the parent is a CI runner, identify the repository, workflow file and triggering commit.
- Run Q10 scoped to that host to determine what credential material was reachable.
- Treat every secret that host could read as compromised and start rotation, do not wait for confirmation of exfiltration.
- If the host is a cPanel/WHM server, run Q9 and native hunts N3/N4, and verify the installed build against section 12.
- Search your GitHub org for the campaign markers (native hunt N2) to find the source of the poisoned workflow.
Pivot queries
- Host-scoped: take Q1 and append
| aid="THE_AID"; run over 90 days to establish first contact. - Environment-wide: run Q6 with no other filter to find every host that has ever referenced the campaign's API paths, including ones where the binary was cleaned up.
- Blast radius: run Q8 to find any other host in the estate exhibiting the same panel-port fan-out.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags ipv4,43.228.157.68,prevent,critical,2027-01-31,GitHub Actions abuse campaign C2 - payload delivery + heartbeat + exfil,campaign:GHActions-cPanel domain,f5b0b742-240a-4811-8a5b-b0ba6060685d.dnshook.site,prevent,critical,2027-01-31,DNSHook out-of-band execution callback,campaign:GHActions-cPanel sha256,22f721fd3a81d2e27cbf90a122bb977f630c50b79daa98350f0e57b04dfa81f1,prevent,critical,2027-01-31,AMD64 Linux scanner and exploitation payload,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/dl/386,detect,critical,2027-01-31,32-bit x86 payload delivery path,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/dl/amd64,detect,critical,2027-01-31,64-bit x86 payload delivery path,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/dl/arm,detect,critical,2027-01-31,32-bit ARM payload delivery path,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/dl/arm64,detect,critical,2027-01-31,64-bit ARM payload delivery path,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/github-heartbeat,detect,critical,2027-01-31,execution telemetry endpoint - 30 second interval,campaign:GHActions-cPanel url,http://43.228.157.68:80/api/github-results,detect,critical,2027-01-31,chunked credential exfiltration endpoint,campaign:GHActions-cPanel filename,/tmp/.svc,detect,high,2026-10-31,scanner drop path on the runner - rename-prone, prefer Q4,campaign:GHActions-cPanel # The 386 / arm / arm64 payload builds were distributed but no hashes were published. # Do NOT invent them - Q4 through Q7 cover those builds behaviourally.
SCANNER_ARGV ipscan + (--exploit | --envdump | --git-workers | --no-reverse | --source random)
DROP_PATTERN dot-prefixed executable, 1-12 char basename, running from /tmp
DOWNLOAD_PATTERN curl or wget -> raw IPv4 host, plaintext http, output into /tmp
C2_PATH_LAYOUT /api/dl/{386,amd64,arm,arm64} + /api/github-heartbeat + /api/github-results
BEACON_CADENCE HTTP POST every ~30s carrying repo identifier + latest scanner log line
EXFIL_SHAPE offset-tracked chunked POST, up to 2000 lines live / 5000 final
PANEL_FANOUT one source host -> many distinct destinations on 2082/2083/2086/2087
PORTSET 80,443,8080,8443,2082,2083,2086,2087 in a single scan invocation
VICTIM_RESIDUE cpsrvd / whostmgrd / cpdavd parent spawning sh, perl, python, curl, wget
WORKFLOW_TRIGGER on: push branches ['**'] + workflow_dispatch, timeout-minutes: 350
RUNNER_ENV GOMEMLIMIT set alongside a PANEL_URL pointing at a raw IPv4
# The maintainer is a VICTIM. These strings are for dependency review only. # Do not block, do not treat as attribution. The account has been suspended. # Affected: branch-based dev versions synced 2026-07-12 / 2026-07-13. dinushchathurya/nationality-list dinushchathurya/srilankan-divisional-secretariats dinushchathurya/srilankan-gn-divisions dinushchathurya/srilankan-local-authorities dinushchathurya/srilankan-mobile-number-validator dinushchathurya/srilankan-state-hospitals dinushchathurya/srilankan-universities dinushchathurya/uk-mobile-number-validator dinushchathurya/uk-post-code dinushchathurya/websmslk # Action: remove the dev version, repin composer.lock to a stable release or a # known-good commit reference predating 2026-07-12, then re-resolve. composer show --installed | grep dinushchathurya composer why dinushchathurya/PACKAGE
# CVE-2026-41940 - CWE-306 missing authentication for critical function # CVSS 3.1 base 9.8 CRITICAL / CVSS 4.0 base 9.3 CRITICAL # CISA KEV: added 2026-04-30, due 2026-05-03, knownRansomwareCampaignUse = Known # # Fixed builds per release tier (NVD affected-version matrix). Upgrade to at # least the build listed for your tier: # 11.40.x -> 11.86.0.41 # 11.88.x -> 11.94.0.28 # 11.96.x -> 11.102.0.39 # 11.104.x -> 11.110.0.97 # 11.112.x -> 11.118.0.63 # 11.120.x -> 11.124.0.35 # 11.126.x -> 11.126.0.54 # 11.128.x -> 11.130.0.19 # 11.132.x -> 11.132.0.29 # 11.134.x -> 11.134.0.20 # 11.136.x -> 11.136.0.5 # WP Squared (WP2) is affected from 11.136.1.7 - follow the vendor changelog. # 1. What build is actually installed? /usr/local/cpanel/cpanel -V # 2. Which panel ports are exposed to the whole internet? ss -ltnp | grep -E ':(2082|2083|2086|2087|2095|2096)\b' # 3. Confirm the upcp updater is enabled and on a release tier that gets fixes grep -E '^(CPANEL|RPMUP|SARULESUP|UPDATES)=' /etc/cpupdate.conf # 4. Run the vendor IOC detection script on any host that was exposed unpatched.
Detection Validation Gates
Gate 1: Telemetry ready
- Confirm Falcon Linux sensors are installed and reporting on every cPanel/WHM host and every self-hosted Actions runner. Run
#event_simpleName=ProcessRollup2 | event_platform=Lin | groupBy([ComputerName])and reconcile the result against your asset inventory. A silent host is not a clean host. - Confirm
DnsRequestevents are present for Linux servers. Many fleets have DNS telemetry only from workstations, which would make Q2 silently useless where it matters most. - Confirm
NetworkConnectIP4is not being sampled or dropped on high-volume hosting servers.
Gate 2: Benign baseline
- Run Q4, Q7, Q8, Q10, Q11 and Q12 over 30 days with no exclusions and record the volume per host group. Any query returning more than ~50 rows per day per host group needs its TUNING line applied before promotion.
- Explicitly enumerate: your build-agent host group (Q4, Q11), your internal package mirrors by IP (Q7), your sanctioned hosting-management jumpboxes (Q8), and your backup/config-management parents (Q10).
Gate 3: Positive tests (safe, defensive only)
- Q1: from an isolated test host, initiate a TCP connection to the C2 address on tcp/80 and confirm the event lands. Do not send data.
- Q2: resolve a benign sub-hostname under the callback domain from a test host and confirm
DnsRequestfires. - Q4: copy
/bin/trueto/tmp/.tst, chmod 755, execute it once, confirm the row appears, then delete it. - Q5: create a harmless script named
ipscanthat only echoes its arguments, invoke it with--envdump --no-reverse, and confirm the match. No scanning, no exploitation. - Q9: on a lab cPanel host only, confirm the parent/child mapping fires by having a test cPanel hook invoke
/bin/sh -c true.
Gate 4: Promotion criteria
- Promote Q5 to IOA-1 (block) immediately: the FP surface is effectively nil.
- Promote Q9 to IOA-2 at Detect severity; move to Block only after two consecutive clean weeks with your cPanel hooks baselined.
- Promote Q4 to IOA-3 at Detect after the build-agent exclusion is in place.
- Q7, Q8, Q10, Q11, Q12 stay as scheduled searches feeding a review queue. Do not page on them.
Hardening: Tiered
- H-I2 · Patch cPanel & WHM to the fixed build for your release tier. This is the whole ballgame. CVE-2026-41940 is CVSS 9.8, CWE-306, on the CISA KEV catalog since 2026-04-30 with a remediation due date of 2026-05-03 and a
Knownransomware-use flag. Fixed builds are listed in the Patch & Exposure Audit block in section 10 and in playbook P1. MITRE M1051 (Update Software) · CISA KEV binding directive · vendor security update 2026-04-28. - H-N1 · Restrict the cPanel/WHM control-plane ports (2082, 2083, 2086, 2087) to trusted networks. The scanner explicitly targets this port set. A control plane that is not internet-reachable cannot be mass-exploited even while you finish patching. MITRE M1030 (Network Segmentation) · MITRE M1035 (Limit Access to Resource Over Network) · CIS Controls v8 4.4 / 4.5.
- H-D1 · Block the campaign C2 and callback hostname at the egress proxy and in Falcon IOC Management. Both are single-purpose actor infrastructure. MITRE M1037 (Filter Network Traffic).
- H-I1 · Require review for any change under
.github/workflows. Enable branch protection with required reviewers and add a CODEOWNERS entry covering the workflows path on every repository. The campaign's entire initial access was an unreviewed push of workflow files. MITRE M1045 (Code Signing) / M1047 (Audit) · GitHub hardening guidance for Actions. - H-C1 · Rotate every secret reachable from any host that matched Q1, Q2, Q3, Q5 or Q9. Assume exfiltration; the payload targets AWS keys, GitHub/GitLab tokens, OpenAI and Google API keys, Stripe keys, SendGrid/Mailgun credentials, DB credentials and SSH material. MITRE M1027 (Password Policies) · M1026 (Privileged Account Management).
- H-N2 · Set the organisation-wide default
GITHUB_TOKENpermission to read-only and require workflows to opt in to write scopes explicitly. This caps what a poisoned workflow can do to your repos even when it runs. MITRE M1018 (User Account Management) · GitHub Actions security hardening guidance. - H-N3 · Restrict which Actions and reusable workflows may run in the org (allow-list local plus verified-creator actions only) and disable Actions entirely on repositories that have no CI. Fewer repositories that can run workflows means fewer that can be weaponised. MITRE M1042 (Disable or Remove Feature or Program).
- H-N4 · Put self-hosted runners behind an egress allow-list and never attach them to public repositories. A self-hosted runner with unrestricted egress is a scanner waiting for a job. MITRE M1030 (Network Segmentation) · M1037 (Filter Network Traffic).
- H-C2 · Remove long-lived static credentials from hosting servers. Move to short-lived, workload-scoped credentials (OIDC federation for cloud, deploy keys scoped to one repo, per-service DB users). The harvest only pays off because the secrets are static and long-lived. MITRE M1026 (Privileged Account Management) · M1041 (Encrypt Sensitive Information) · CIS Controls v8 5.2 / 6.5.
- H-N5 · Mount
/tmpwithnoexec,nosuid,nodevon servers and CI runners. The payload is dropped to/tmpand chmod-ed to 755; anoexecmount breaks that step outright. Pilot carefully: some package managers and installers use/tmpfor executable scratch space. MITRE M1022 (Restrict File and Directory Permissions) · CIS Distribution Independent Linux Benchmark §1.1 (separate partition with noexec/nosuid/nodev for /tmp). - H-N6 · Enable GitHub secret scanning with push protection across the org, so a token harvested from a server and reused in a commit is caught. MITRE M1047 (Audit).
- H-S1 · Treat CI configuration as production code. Workflow files execute with real access to build systems and secrets; put them under the same review, testing, change-control and monitoring you apply to application code. This is the structural lesson of the campaign, not an optional refinement. MITRE M1047 (Audit) · NIST SSDF PO.3 / PW.4 / PS.1 · SLSA build-integrity levels.
- H-S2 · Pin dependencies to immutable commit references and stop consuming branch-based dev versions. The poisoned artefacts were
dev-*branch versions that Packagist synced automatically; a lockfile pinned to a reviewed commit would not have moved. Apply the same rule to third-party Actions (pin to a full commit SHA, not a tag). MITRE M1051 (Update Software) · NIST SSDF PW.4.1 · SLSA provenance. - H-S3 · Move the hosting control plane behind a VPN or identity-aware proxy. cPanel and especially WHM are administrative control planes for many customer environments; they should not answer unauthenticated requests from the internet at all. MITRE M1030 (Network Segmentation) · M1032 (Multi-factor Authentication) · CIS Controls v8 12.7 / 13.4.
- H-S4 · Build a CI egress monitoring capability. Route all runner traffic through a logged proxy and alert on connections to raw IP addresses, on newly-seen destinations, and on any egress from a job whose declared purpose is a build. MITRE M1037 (Filter Network Traffic) · M1047 (Audit). ⚠ best-practice, no formal benchmark
- H-S5 · Inventory every secret that lives on a hosting server and drive that count toward zero via a secrets manager with short-lived leases. You cannot scope the blast radius of a server compromise you cannot enumerate. MITRE M1041 (Encrypt Sensitive Information) · CIS Controls v8 3.11.
What hardening cannot fix here: the scanning traffic originates from GitHub-hosted runner address space. Blocking source IPs would mean blocking GitHub, and the actor loses nothing when a runner is torn down. Patch state and control-plane exposure are the only variables you actually control on the victim side.
Deployable Playbooks
P1 · Patch and verify cPanel / WHM (Linux, root)
Prerequisites: root on the cPanel/WHM host; a current full backup or a VM snapshot; a maintenance window (upcp restarts panel services and briefly interrupts panel access); knowledge of your current release tier. Reboot required: no: upcp restarts cpsrvd and related services, but no host reboot is needed unless the update also pulls a kernel. Rollback: cPanel does not support downgrading across a release tier. Roll back by restoring the pre-update VM snapshot or backup taken in step 1: that snapshot is the rollback plan, so do not skip it.
# 1. Snapshot / back up FIRST. This is the only rollback path. /usr/local/cpanel/scripts/pkgacct --skipbwdata USERNAME # per-account, or take a VM snapshot # 2. Record the current build so you can prove what changed. /usr/local/cpanel/cpanel -V | tee /root/cpanel-version-before.txt # 3. Confirm the update tier. RELEASE or STABLE both receive the fix; a frozen # or manually pinned tier will NOT. grep -E '^(CPANEL|UPDATES)=' /etc/cpupdate.conf # 4. Update. /usr/local/cpanel/scripts/upcp --force # 5. Verify against the fixed build for your tier (see section 10 audit block). /usr/local/cpanel/cpanel -V | tee /root/cpanel-version-after.txt # 6. Confirm the panel services came back. systemctl status cpanel; ss -ltnp | grep -E ':(2083|2087)\b'
P2 · Restrict the cPanel / WHM control plane to trusted networks (firewalld)
Prerequisites: root; firewalld active; the exact CIDR ranges your administrators connect from, confirmed in advance. Reboot required: no. Rollback: every command below has an explicit undo in the rollback block, run it to restore the previous state. Test from a second, already-open session before you close the first: locking yourself out of a remote host is the realistic failure mode here.
# Replace 198.51.100.0/24 with YOUR administrative range before running anything.
ADMIN_NET="198.51.100.0/24"
# 1. Create a trusted source zone and put the panel ports in it.
firewall-cmd --permanent --new-zone=cpanel-admin
firewall-cmd --permanent --zone=cpanel-admin --add-source="$ADMIN_NET"
for P in 2082 2083 2086 2087 2095 2096; do
firewall-cmd --permanent --zone=cpanel-admin --add-port=${P}/tcp
done
# 2. Remove those ports from the public zone.
for P in 2082 2083 2086 2087 2095 2096; do
firewall-cmd --permanent --zone=public --remove-port=${P}/tcp
done
# 3. Apply, then VERIFY FROM A SECOND SESSION before closing this one.
firewall-cmd --reload
firewall-cmd --zone=cpanel-admin --list-all
firewall-cmd --zone=public --list-ports
# --- ROLLBACK ---------------------------------------------------------------
# for P in 2082 2083 2086 2087 2095 2096; do
# firewall-cmd --permanent --zone=public --add-port=${P}/tcp
# done
# firewall-cmd --permanent --delete-zone=cpanel-admin
# firewall-cmd --reload
P3 · Harden /tmp against payload execution (Linux servers and CI runners)
Prerequisites: root; /tmp already on its own filesystem, or willingness to move it to a tmpfs mount; a pilot ring: some package managers and installers execute from /tmp and will break. Reboot required: no if you remount live; yes to prove the fstab change survives a boot, which you should do in the pilot ring. Rollback: restore the saved /etc/fstab copy and remount with exec, shown in the rollback block.
# 1. Back up fstab. This is the rollback artefact. cp -a /etc/fstab /root/fstab.bak-$(date +%F) # 2. Check whether /tmp is already a separate filesystem. findmnt /tmp # 3a. If /tmp IS a separate mount, add the options. sed -i.huntpack -E 's|^(\S+\s+/tmp\s+\S+\s+)([^ ]+)|\1\2,noexec,nosuid,nodev|' /etc/fstab # 3b. If /tmp is NOT separate, mount it as tmpfs instead. # echo 'tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0' >> /etc/fstab # 4. Apply live and verify. mount -o remount /tmp findmnt -no OPTIONS /tmp # expect noexec,nosuid,nodev present # 5. Prove the control works: this must now FAIL. cp /bin/true /tmp/.hp-test && chmod 755 /tmp/.hp-test && /tmp/.hp-test; echo "exit=$?" rm -f /tmp/.hp-test # --- ROLLBACK --------------------------------------------------------------- # cp -a /root/fstab.bak-YYYY-MM-DD /etc/fstab # mount -o remount,exec /tmp # findmnt -no OPTIONS /tmp
P4 · Lock down GitHub Actions at the organisation level
Prerequisites: org owner rights; gh authenticated with admin:org scope; agreement from engineering leads, because read-only default token permissions will break pipelines that silently relied on write access. Reboot required: none: these are API-side settings, effective on the next workflow run. Rollback: each setting has an inverse API call in the rollback block; capture the current values in step 0 first so you can restore exactly what was there.
ORG="YOUR_ORG" # 0. CAPTURE CURRENT STATE FIRST - this is your rollback record. gh api "orgs/$ORG/actions/permissions" | tee /tmp/gh-actions-before.json gh api "orgs/$ORG/actions/permissions/workflow" | tee -a /tmp/gh-actions-before.json # 1. Default GITHUB_TOKEN permissions to read-only; workflows must opt in. gh api -X PUT "orgs/$ORG/actions/permissions/workflow" \ -f default_workflow_permissions=read \ -F can_approve_pull_request_reviews=false # 2. Allow only local + verified-creator actions. gh api -X PUT "orgs/$ORG/actions/permissions" \ -f enabled_repositories=all -f allowed_actions=selected gh api -X PUT "orgs/$ORG/actions/permissions/selected-actions" \ -F github_owned_allowed=true -F verified_allowed=true -f 'patterns_allowed[]=' # 3. Require review on the workflows path in each repo (CODEOWNERS). # Commit this to .github/CODEOWNERS on the default branch: # /.github/workflows/ @YOUR_ORG/ci-reviewers # 4. Verify. gh api "orgs/$ORG/actions/permissions/workflow" # --- ROLLBACK --------------------------------------------------------------- # Replay the values captured in /tmp/gh-actions-before.json: # gh api -X PUT "orgs/$ORG/actions/permissions/workflow" \ # -f default_workflow_permissions=write -F can_approve_pull_request_reviews=true # gh api -X PUT "orgs/$ORG/actions/permissions" -f allowed_actions=all
P5 · Falcon-side blocking (IOC Management + Custom IOA)
Prerequisites: Falcon role with IOC Management write and Custom IOA authoring; a chosen host group for the IOA rule group; agreement that prevention (not just detection) is wanted. Reboot required: none. Rollback: disable the IOA rule group (do not delete it: you lose the tuning history) and set the IOC actions back to no_action; both undos are shown below.
# 1. Import the atomic indicators (section 10 CSV) via
# Endpoint security > IOC management > Add indicators > Upload CSV.
# Set: ipv4 + domain + sha256 -> Prevent/Block, severity Critical.
# Set: url rows -> Detect (URL matching depends on module licensing).
# 2. Create the IOA rule group.
# Endpoint security > Custom IOA rule groups > Create rule group
# Platform: Linux Name: GHActions-cPanel-Campaign
#
# IOA-1 Process Creation ACTION: Block Execution SEVERITY: Critical
# Command Line.*\bipscan\b.*
# Command Line.*(--exploit|--envdump|--git-workers|--no-reverse).*
#
# IOA-2 Process Creation ACTION: Detect SEVERITY: High
# Parent Image Filename.*\x2f(cpsrvd|cpsrvd-ssl|whostmgrd|cpdavd)$
# Image Filename.*\x2f(sh|bash|dash|perl|python3?|curl|wget|nc|socat)$
#
# IOA-3 Process Creation ACTION: Detect SEVERITY: High
# Image Filename.*\x2ftmp\x2f\.[A-Za-z0-9_-]{1,12}$
# 3. Assign the rule group to a PILOT host group first. Promote to the full
# Linux group only after Gate 2 baselining in section 11 is clean.
# --- ROLLBACK ---------------------------------------------------------------
# Custom IOA rule group -> toggle Enabled = off (keep the group; deleting it
# discards every exclusion you tuned).
# IOC management -> select the campaign tag -> set action to "No action".
Containment Runbook
| Phase | Actions | Owner | Evidence to capture |
|---|---|---|---|
| 0 · Triage 0–30 min |
Confirm the hit is campaign-related: run Q1 and Q2 scoped to the host. Determine the host's role: cPanel/WHM server, self-hosted runner, or developer workstation, because the response diverges immediately after this step. | SOC L2 | Falcon detection ID, aid, full process tree, matching command lines |
| 1 · Isolate 0–1 hr |
Network-contain the host in Falcon. If it is a self-hosted runner, deregister it from the org and drain its job queue. If it is a cPanel/WHM server, restrict the panel ports to admin ranges (playbook P2) rather than taking hosting offline, unless the compromise is confirmed. Suspend any GitHub repository actively running the poisoned workflow. | SOC L2 + platform owner | Containment timestamp, runner registration record, repo and workflow-run URLs |
| 2 · Scope 1–4 hrs |
Run Q1 and Q6 estate-wide over 90 days to find every other host that touched the C2 or its API paths. Run Q8 for other hosts exhibiting panel-port fan-out. Run native hunt N2 across your GitHub org to find every poisoned workflow file. Run N5 to find every project carrying an affected package version. | Threat hunting | Host list with first/last contact times; list of repos and workflow files; lockfile inventory |
| 3 · Eradicate 4–24 hrs |
Delete the malicious workflow files and the branches that carried them, but preserve commits and Actions run logs first, deleting them destroys the only record of what ran. Remove the dropped binary and capture its hash before deletion. On cPanel/WHM hosts: patch (playbook P1), then run the vendor IOC detection script, then invalidate every panel session and purge /var/cpanel/sessions/ after evidence capture. |
IR + platform owner | Binary sample and SHA-256, workflow file contents, Actions run logs, session-file archive from native hunt N4 |
| 4 · Rotate 24–72 hrs |
Rotate every credential the affected host or pipeline could reach, on the assumption they left: AWS keys, GitHub/GitLab tokens (including any PAT or deploy key on the host), OpenAI and Google API keys, Stripe keys, SendGrid/Mailgun credentials, database credentials, SSH private keys and their authorized_keys entries. Review OAuth apps and GitHub App installations for grants you did not make. Revoke, then reissue, do not merely reissue. | IAM + cloud + app owners | Rotation ledger with per-credential timestamps; OAuth/App grant diff |
| 5 · Recover 3–7 days |
Rebuild self-hosted runners from a known-good image rather than cleaning them; an ephemeral runner is cheaper to replace than to trust. Repin composer.lock to stable releases or commit references predating 2026-07-12 and re-resolve. Restore panel-port access only after patch verification. Return contained hosts to production behind the near-term hardening in section 12. |
Platform + engineering | Rebuild records, lockfile diffs, post-patch cpanel -V output |
| 6 · Watch 30 days |
Keep Q1, Q2, Q5, Q6 and Q9 running as scheduled searches. Treat the operation as ongoing: the maintainer account was suspended but forks, mirrors, cached snapshots, stolen credentials and the C2 may all remain live. Re-run native hunt N2 weekly against your org. | SOC | Weekly hunt results, scheduled-search hit counts |
Do not skip evidence capture to move faster. Deleting a poisoned workflow file and its Actions run log is the single most common way an organisation loses the ability to answer "what secrets did it read". Capture first, then eradicate.
Detection Coverage Map
| Technique | Behaviour | CQL | IOA / IOC | Coverage |
|---|---|---|---|---|
| T1584.006 | Compromised repos + hosted runners as attack infrastructure | none | none | GAP: no EDR reach into github.com; covered only by native hunts N1/N2 |
| T1195.001 | Malicious workflow files pushed into .github/workflows | Q12 | none | Partial, only where the write happens on an instrumented host |
| T1195.001 | Poisoned Packagist dev versions in a lockfile | none | Audit block | Partial: no telemetry; dependency audit N5 only |
| T1082 | Runner CPU architecture detection | Q11 | none | Partial, self-hosted runners only |
| T1105 | ELF payload download from the C2 | Q1, Q6, Q7 | IOC (ipv4, url) | Good |
| T1105 | Payload written to /tmp/.svc and chmod 755 | Q3, Q4 | IOA-3, IOC (sha256) | Good, amd64 by hash, all builds behaviourally |
| T1059.004 | Unix shell execution inside the workflow run step | Q5, Q7, Q11 | IOA-1 | Good on instrumented hosts; GAP on GitHub-hosted runners |
| T1595.002 | Internet-wide vulnerability scanning | Q5, Q8 | IOA-1 | Good |
| T1190 | CVE-2026-41940 exploitation against cPanel/WHM | Q9 | IOA-2 | Partial: the bypass itself is invisible to EDR; only the post-exploitation child process and the cPanel logs (N3/N4) show it |
| T1496.002 | Bandwidth/compute hijacking of Actions runners | Q8 | none | Partial, inferred from fan-out, not measured directly |
| T1552.001 / T1005 / T1119 | Credential and config file harvesting | Q10 | none | Partial, high FP, correlation input only |
| T1074.001 | Results staged in local output files on the runner | none | none | GAP: staging happens on ephemeral hosted runners |
| T1071.001 | HTTP POST C2 on tcp/80 | Q1, Q6 | IOC (ipv4, url) | Good |
| T1071.004 | DNSHook out-of-band callback | Q2 | IOC (domain) | Good |
| T1020 / T1041 | Chunked, offset-tracked exfiltration over the C2 channel | Q1, Q6 | IOC (url) | Good |
Known gaps and what to do about them
- GitHub-hosted runners are unreachable. Three chain steps (3, 4 and part of 8) execute on third-party ephemeral compute. Nothing in Falcon will ever see them. Native hunts N1 and N2 plus the org-level controls in playbook P4 are the entire coverage, and they are preventative and retrospective rather than detective.
- The authentication bypass leaves no EDR signal. CVE-2026-41940 is exploited inside the cpsrvd process; nothing spawns until the attacker acts. Q9 catches what they do next, and native hunts N3/N4 catch the on-disk residue. If the attacker only reads data through the panel API, neither will fire, which is why patching (H-I2) is not optional and cannot be substituted with detection.
- Only one payload hash is public. Three of four architecture builds have no published hash. Do not fill that gap with an invented hash; Q4 through Q7 cover them behaviourally.
- Data staging on the runner is unobservable. Accepted gap. Exfiltration is still caught at the network layer by Q1 and Q6.
Validation gates for all of the above are in section 11: confirm Linux telemetry is actually flowing (Gate 1) before reading any empty result as a clean result, baseline the six noisy queries for 30 days (Gate 2), run the safe positive tests (Gate 3), then promote Q5, Q9 and Q4 in that order (Gate 4).
Hunt Summary Ticket
TITLE: GitHub Actions Abuse - Distributed cPanel/WHM Exploitation (CVE-2026-41940)
SEVERITY: Critical - CVSS 3.1 9.8, CISA KEV since 2026-04-30, known ransomware use
SCOPE: 1) Every internet-facing cPanel or WHM server (victim side)
2) Every self-hosted GitHub Actions runner (attacker-infrastructure side)
3) Every GitHub repository owned by the org (poisoned workflow search)
4) Every PHP/Composer project with a branch-based dev dependency
HYPOTHESIS: Poisoned .github/workflows files execute on ephemeral runners, pull an
architecture-matched Linux ELF from 43.228.157.68 to /tmp/.svc, scan the
internet for cPanel/WHM on 2082/2083/2086/2087, exploit CVE-2026-41940,
harvest cloud + payment + source-control credentials, and exfiltrate them
in chunked HTTP POSTs with a 30-second heartbeat.
QUERIES: Q1 C2 contact 43.228.157.68 CONF high FP low
Q2 DNSHook callback resolution CONF high FP low
Q3 Payload SHA-256 (amd64 build) CONF high FP low
Q4 Dot-prefixed binary executed from /tmp CONF high FP med
Q5 Scanner argv (ipscan --exploit --envdump) CONF high FP low
Q6 C2 API paths on a command line CONF high FP low
Q7 curl/wget from raw IPv4 over http into /tmp CONF med FP med
Q8 Panel-port fan-out (2082/2083/2086/2087) CONF high FP med
Q9 cpsrvd/whostmgrd spawning a shell CONF high FP low
Q10 Credential material enumeration CONF med FP high
Q11 Self-hosted runner spawning network tooling CONF med FP med
Q12 Bulk .github/workflows manipulation CONF med FP high
DO FIRST: 1. Patch cPanel/WHM to the fixed build for your tier (playbook P1).
KEV due date was 2026-05-03 and is long past.
2. Run Q1 and Q2 estate-wide at 90 days.
3. Run Q9 across every hosting server at 30 days.
4. Search the GitHub org for campaign markers (native hunt N2).
5. Restrict panel ports 2082/2083/2086/2087 (playbook P2).
FINDINGS: [ ] Hosts contacting the C2: ____
[ ] Hosts resolving the DNSHook host: ____
[ ] Payload hash sightings: ____
[ ] cPanel servers below the fixed build: ____
[ ] Poisoned workflow files in-org: ____
[ ] Projects carrying an affected dev version: ____
GAPS: - GitHub-hosted runners: no EDR coverage possible, ever.
- The auth bypass itself is invisible to EDR; only post-exploitation
child processes and cPanel session/login logs show it.
- Only the amd64 payload hash is public; 386/arm/arm64 covered
behaviourally by Q4-Q7. No hash was invented for them.
- Data staging on the runner is unobservable. Accepted.
ACTIONS: - Promote Q5 to a blocking Custom IOA (IOA-1) immediately.
- Promote Q9 to IOA-2 at Detect; Block after two clean weeks.
- Promote Q4 to IOA-3 after excluding build agents.
- Block the C2 IP, callback hostname and payload hash in IOC Management.
- Set org default GITHUB_TOKEN permissions to read-only (playbook P4).
- Rotate every secret reachable from any matching host. Assume exfil.
OWNER: SOC threat hunting / platform engineering (joint)
VERSION: v0.1 - 2026-07-31 - HuntPack
Changelog
References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| 1 | Socket: Large-Scale GitHub Actions Abuse Powers a Distributed cPanel and WHM Exploitation Campaign (2026-07-22) | Primary technical writeup: workflow structure, payload URLs, scanner argv, heartbeat and exfil endpoints, DNSHook hostname, payload hash, ATT&CK mapping, full IOC set | 2026-07-31 |
| 1 | NVD: CVE-2026-41940 | CVSS 3.1 9.8 / CVSS 4.0 9.3, CWE-306, affected and fixed build matrix for cPanel, WHM and WP Squared | 2026-07-31 |
| 1 | CISA Known Exploited Vulnerabilities Catalog | KEV entry: added 2026-04-30, due 2026-05-03, known ransomware campaign use | 2026-07-31 |
| 2 | watchTowr Labs: cPanel / WHM authentication bypass analysis | Root-cause mechanism and the on-disk forensic artefacts used in native hunts N3/N4. Exploitation steps and the linked PoC are deliberately not reproduced in this pack | 2026-07-31 |
| 2 | The Hacker News: Attackers weaponize GitHub Actions (2026-07-23) | Independent corroboration of file counts, timeline, C2 address and DNSHook identifier | 2026-07-31 |
| 2 | Cyber Security News: Hackers abuse GitHub Actions to exploit cPanel and WHM servers (2026-07-23) | Second attestation of the complete IOC table including the drop path | 2026-07-31 |
| 2 | SecurityOnline: GitHub Actions abuse, cPanel and WHM exploitation | Campaign scale framing, attribution caveat, panel-port restriction guidance | 2026-07-31 |
| 2 | cPanel & WHM Security Update 04-28-2026 (support.cpanel.net) | Not cited as evidence: returned HTTP 403 to every fetch attempt, so no snapshot exists. Listed only so the reader knows the vendor advisory exists and should be consulted directly for the current IOC detection script | 2026-07-31 (unfetchable) |
| Ref | MITRE ATT&CK: T1195.001, T1584.006, T1059.004, T1105, T1082, T1496.002, T1595.002, T1190, T1552.001, T1005, T1119, T1074.001, T1071.001, T1071.004, T1020, T1041 and mitigations M1018, M1022, M1026, M1027, M1030, M1032, M1035, M1037, M1041, M1042, M1045, M1047, M1051 | Technique and mitigation mapping | 2026-07-31 |
Provenance: the fetched plain text of every cited source above is saved verbatim in GitHubActions-cPanel-CVE-2026-41940-Hunt-sources/ (7 files). Every atomic indicator in section 10 traces back to at least one of those snapshots. Nothing in this pack is an indicator that no source published.