Nine weeks open. One HTTP trick. Root on 1.5 million internet-exposed shared-hosting servers.

The vendor didn't know. The hosting providers didn't know. The customers didn't know. By the time cPanel shipped an emergency patch on April 28, 2026, exploitation had been silently underway since February 23.

The flaw is CVE-2026-41940. CVSS 9.8. Affects every version of cPanel and WHM after 11.40, including DNSOnly and WP Squared. A handful of HTTP requests. Zero credentials. Six lines injected into a session file. Root on the host. Root on every customer account, every database, every mailbox, every DNS zone the host serves.

By the time the patch shipped, the damage was already telemetric. Shadowserver: 44,000 unique IPs scanning or exploiting. Censys: 7,135 confirmed cPanel/WHM hosts with .sorry files written across them — the signature of a Go-based Linux ransomware that wipes backups before it encrypts. CISA: KEV catalog, three-day patch deadline for federal civilian agencies. State-aligned actors already in the picture, hitting Philippine military domains, Lao government domains, and MSPs in Canada, the US, and South Africa.

And then the question that turns a bad CVE into a worse one: was it ignored?

A webhosting.today source quoted by Help Net Security says the vulnerability was responsibly disclosed to cPanel about two weeks before the public advisory — and cPanel's initial response was that nothing was wrong. Unverified by primary sources, but plausible. The exploit was already months old. The technical writeup that would later make it obvious wasn't public. The symptoms in the wild were buried inside hosting providers' own incident queues. If true, that's another fortnight on a window that was already nine weeks too long. cPanel has not publicly addressed the timeline.

Mechanically: CRLF injection into the pass field via HTTP Basic Auth, combined with a whostmgrsession cookie missing its ob segment so the symmetric encoder that would normally hex-encode the password never fires. Arbitrary keys land in the on-disk session file. A helper function (do_token_denied) promotes them from the raw text file into the JSON cache. From that moment on, every request is treated as authenticated root. The full chain is below.

CVE-2026-41940 surfaced in the Radar feed on April 30, 2026, the same day CISA added it to KEV. The entry was sparse — what CISA published, no more. The technical analysis, telemetry, and disclosure reporting all landed in the days that followed. This post is the retrospective.

What's signal:

  • Shadowserver: ~44,000 unique IPs scanning or exploiting at peak on April 30. ~650,000 exposed cPanel/WHM instances overall, with ~1.5 million internet-facing instances per Shodan counts cited by Rapid7.
  • Censys: 8,859 hosts exposing open directories with .sorry filenames; 7,135 confirmed cPanel/WHM.
  • CISA added it to the KEV catalog on April 30 with a three-day patch deadline for FCEB agencies.
  • The compromised-IP figure dropped to ~3,540 by May 3 as cleanup and automatic updates caught up.

What's noise: "44,000 servers encrypted overnight" — that's a peak figure for scanning and exploit attempts, not a single-night compromise count. Generic blog-spam articles pushing specific third-party migration or backup tools as if they're disaster-recovery essentials. Claims that the bug affects "all hosting control panels" — it's specific to cPanel's CRLF handling in the Basic Auth flow. Plesk has its own codebase and needs to be tracked separately.


Verified facts

ItemValueSource
CVE IDCVE-2026-41940NVD, cPanel TSR
CVSS9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), CVSSv4 9.3NVD, Cato Networks
CWECWE-306 (Missing Authentication for Critical Function) per NVD; cPanel cites CWE-93 (CRLF in HTTP headers)NVD, cPanel
AffectedAll cPanel/WHM versions after 11.40, including DNSOnly. WP Squared before 136.1.7cPanel, Rapid7
Patch releasedApril 28, 2026cPanel TSR
Earliest observed exploitationFebruary 23, 2026KnownHost, confirmed by Help Net Security
Reported to cPanel~2 weeks before April 28; per a webhosting.today source, cPanel's initial response was that "nothing was wrong"Help Net Security
Vulnerability typeAuthentication bypass via CRLF injection in session handling, combined with an encoder bypasswatchTowr Labs
Patched filesCpanel/Session.pm, Cpanel/Session/Load.pm, Cpanel/Session/Encoder.pmwatchTowr diff
KEV listingApril 30, 2026CISA
Peak scanning/exploit volume~44,000 unique IPs (April 30)Shadowserver
Hosts with .sorry artifacts7,135 confirmed cPanel/WHM (out of 8,859 total)Censys
Ransomware family"Sorry" — Go-based Linux encryptor, Tox contactHelp Net Security
Other payloads observedMirai variants, OpenVPN, Ligolo (persistence)Ctrl-Alt-Intel
Observed source ASNsDigitalOcean, Amazon, Latitude.sh; geolocated to Ireland, Japan, USCato Networks

Patched targets: 11.110.0.97, 11.118.0.63, 11.126.0.54, 11.132.0.29, 11.134.0.20, 11.136.0.5. WP Squared: 136.1.7.


How the bypass actually works

Sina Kheirkhah at watchTowr Labs published the definitive breakdown on April 29. Here's the core, restructured.

1. The session model

When a login POST hits port 2087 — even with bad credentials — cpsrvd mints a preauth session and writes two files:

  • /var/cpanel/sessions/raw/<id> — flat text, key=value per line
  • /var/cpanel/sessions/cache/<id> — JSON-serialized hash of the same data

The cookie returned has the form :<rand>,<obhex>. The trailing 32 hex characters after the comma are a per-session secret called ob, used by Cpanel::Session::Encoder to symmetrically encode the pass field before it reaches disk.

2. Two bugs that compose

Bug A: CRLF injection. When a request arrives with Authorization: Basic <base64>, the value is decoded into <user>:<pass>. set_pass only strips NUL bytes. \r\n survives. When pass is then written to the session file via saveSession — without going through Cpanel::Session::create or filter_sessiondata — those line breaks land raw on disk. The contents of pass= get parsed as multiple top-level keys on the next read.

Bug B: Encoder bypass. The encoder derives its secret from the ob segment of the cookie. If the cookie is sent without the comma and hex tail, $ob is empty, the encoder is never instantiated, and pass is written unmodified in cleartext — including any injected CRLFs. Stripping the tail doesn't change file path resolution, so the same session file is reachable with either form of the cookie.

Combined: send a valid session cookie without the ob tail, and a Basic Auth header where pass contains \r\nhasroot=1\r\ntfa_verified=1\r\n…. On disk you get:

pass=x
hasroot=1
tfa_verified=1
user=root
cp_security_token=/cpsess9999999999
successful_internal_auth_with_timestamp=1777462149

3. The cache trap

You'd expect that to be enough. It isn't. loadSession reads the JSON cache first, not the raw file. And JSON serializes string values with embedded \r\n as escape sequences inside a single field — so the injection ends up trapped inside the pass string, not as top-level keys. The loader sees no hasroot=1.

watchTowr found the lever: Cpanel::Session::Modify::new opens the session file with nocache => 1, parses the raw file as line-based config, and Modify::save writes the resulting hash back to both the raw file and the JSON cache. The function do_token_denied in cpsrvd calls exactly Modify::new followed by Modify::save — and it triggers the moment you send an authenticated request to any endpoint without a valid cp_security_token in the URL.

After that call, the JSON cache contains hasroot: "1", user: "root", successful_internal_auth_with_timestamp: "..." as real top-level fields.

4. Shadow bypass via timestamp

Even with the forged session, cpsrvd runs docheckpass_whostmgrd on every request, which would normally validate against /etc/shadow. But the function first checks whether successful_external_auth_with_timestamp or successful_internal_auth_with_timestamp is set. If either exists, check_authok_user returns AUTH_OK unconditionally. Shadow is never consulted.

That's why successful_internal_auth_with_timestamp was in the payload from the start — it's the key that means the attacker never needs the actual root password.

5. 2FA loses because the flag is just a flag

The question "why didn't 2FA stop this?" has a simple answer: 2FA state is stored as tfa_verified=1 in the same session file the attacker just wrote. When the server reads its cache and sees "tfa_verified": "1", it assumes the challenge passed. There's no external verification, no HMAC, no cryptographic binding to anything. A flag is just a flag.

6. The Swiss cheese

Each layer failed for a recognizable reason:

  1. Sanitization was opt-in, not opt-out. filter_sessiondata already existed but had to be called by every caller. The Basic Auth path forgot. The patch moves sanitization into saveSession itself.
  2. The per-session secret comes from the cookie. Sounds odd, but it's a cache-friendly design choice — and it means the attacker controls whether the encoder runs at all.
  3. Two parallel persistence layers (raw + JSON cache) that don't share a parser. Cache wins on load. Raw wins on certain modify operations. Manipulation can be moved between them.
  4. Authentication state persisted as a plain key in a mutable file. No cryptographic binding between session ID and auth state.

Each individual flaw was survivable. The composition gave you 9.8.


Severity framework

CVSS 9.8 only tells half the story. What makes this especially severe is the combination of factors:

Pre-auth, network-reachable. No credentials, no user interaction. A handful of HTTP calls to 2087/2083 is enough.

Privilege level. WHM is root on the host. Successful exploitation isn't a user account, it's the keys to the machine — every DNS zone, every SSL certificate, every customer cPanel account on the server.

Multi-tenant blast radius. One compromised WHM host equals every hosted account on it. On shared hosting providers, that can be thousands of sites per node.

Patch window vs. exploit window. The vulnerability was reportedly disclosed to cPanel about two weeks before the patch shipped, and exploited in the wild for approximately nine weeks before the patch — meaning roughly seven of those weeks happened before the responsible disclosure. Even after patching, every server that was internet-exposed between February 23 and April 28 has to be assumed potentially backdoored — patching alone doesn't remediate prior compromise.

Exposed installed base. Shodan counts cited by Rapid7 show ~1.5 million internet-facing cPanel instances. Shadowserver's honeypot telemetry tracks ~650,000 exposed cPanel/WHM endpoints. cPanel is the dominant control panel for commercial Linux shared hosting — though precise market share varies sharply by methodology (6sense places it at 94% within a narrow category that excludes Plesk; W3Techs detection-based data has Plesk leading at ~46%; Datanyze places cPanel at ~22% of the commercial installed base).

Worst case for an average WHM host

  1. Pre-auth root via CVE-2026-41940
  2. Webshell + cron + SSH key + Ligolo or OpenVPN dropped for persistence
  3. Exfiltration of /etc/shadow, all MySQL databases, all mailboxes
  4. Local backups wiped (Acronis agents, JetBackup archives stored on the same host)
  5. "Sorry" Go encryptor deployed — ChaCha20 per-file with RSA-2048 wrap of the symmetric keys. Without the attacker's private key, decryption is mathematically infeasible.
  6. Ransom note dropped in every web root, Tox contact instructions
  7. If the host is an MSP or hosting provider: lateral spread across the entire customer portfolio

For state-sponsored actors (Ctrl-Alt-Intel observed campaigns against *.mil.ph, *.gov.la, and MSPs in Canada, South Africa, and the US), the goal is rarely ransomware — it's long-term residency. That's harder to detect: no .sorry files, no ransom note. Just a way back in.


What's overstated, wrong, or misleading in the public discussion

"44,000 servers were encrypted overnight." No. 44,000 is Shadowserver's count of unique IPs scanning or attempting exploitation, observed across their sensor network. The number of confirmed .sorry-bearing hosts is 7,135 per Censys. The difference is large and matters.

"It's fixed by patching." Patching stops new exploitation. It doesn't remove a backdoor planted during the nine-week zero-day window. If a server was internet-exposed between February 23 and April 28, it has to be treated as potentially compromised: review the sessions directory, audit WHM users, SSH keys, cron, run cPanel's detection script, and when in doubt rebuild from clean media.

"2FA saved us." Not in this case. tfa_verified is a flat boolean in a session file the attacker controls. The only real countermeasure is network segmentation — taking 2087/2083 off the public internet.

Affiliate pushing of third-party migration or backup tools. Some of the articles that surfaced after the disclosure carry oddly specific product placements (the SysTools-pattern blogs are an obvious example). If you're reading something that drops three different product names inside a CVE analysis, treat it as marketing in technical clothing. The honest vendor guidance is simpler: cPanel's own detection script, a real off-host backup (JetBackup or equivalent), and standard incident response.

"Plesk is also vulnerable." No. CVE-2026-41940 is specific to cPanel's CRLF handling in the Basic Auth flow. Plesk is a separate codebase with separate advisories.

"ChaCha20 + RSA-2048 means there's a way to break it." There isn't. Symmetric per-file encryption with the per-file key wrapped in the attacker's RSA public key is a standard modern ransomware construction. Without the private key, recovery from ciphertext is not mathematically feasible with current technology. The way back is off-host backup. That's the entire answer.


Detection and remediation

cPanel's official detection script looks for signatures under /var/cpanel/sessions/raw/:

  • pass= values containing embedded \r or \n
  • pass= followed immediately by what looks like another key=value line
  • token_denied together with cp_security_token in the same preauth session
  • tfa_verified=1 in a session that never went through the 2FA flow
  • Multiple pass= lines in the same file

Hadrian published a Nuclei template (cve-2026-41940-native.yaml) that runs the full exploit chain against a target and verifies by extracting the version string from /json-api/version. Only vulnerable hosts produce output. watchTowr has its own detection artifact generator on GitHub.

Post-patch tasks:

  1. Run /usr/local/cpanel/cpanel -V and confirm the patched build.
  2. Restart cpsrvd and cpdavd.
  3. Force password rotation on root and all WHM users.
  4. Rotate all API tokens (WHM, cPanel API, reseller).
  5. Audit /var/log/wtmp, WHM access logs, and cpsrvd_log for the February 23 to April 28 window.
  6. Inspect /var/cpanel/sessions/raw/* for the IOCs listed above.
  7. Audit cron, ~/.ssh/authorized_keys for every user, /etc/passwd for new accounts.
  8. If the server was exposed: rebuild from clean media as the default position, not the exception.

Cato observed exploitation from ASNs tied to DigitalOcean, Amazon, and Latitude.sh, geolocated to Ireland, Japan, and the US. Useful for retrospective log grep, but not exhaustive — multi-actor exploitation means the ASN pattern spreads quickly.

As a temporary mitigation before patching is complete: block 2083, 2087, 2095, 2096 at the perimeter, or stop cpsrvd and cpdavd outright.


Lessons for those of us building alternatives

There's something to reflect on here that isn't strictly technical. cPanel/WHM dominates shared hosting because it's easy to adopt and because the surrounding ecosystem (WHMCS, JetBackup, Imunify360, CSF, ConfigServer) makes it operationally rational. It's also a shared single point of failure for a large slice of the mid-tier web.

When a CVSS 9.8 lands and the dominant share of commercial Linux shared hosting has to patch, that's not just a vendor problem — it's a structural one. The concentration is the risk.

On April 13, 2026, WAYSCloud published WAYSCLOUD-TR-2026-0011: a planned migration of our shared hosting platform from a commercial control panel to HestiaCP, scheduled for May 31. The stated reasons in that report are open-source alignment across the stack and rising commercial license costs. Security wasn't in the rationale. The CVE-2026-41940 disclosure landed fifteen days later, on April 28.

I'm not going to claim foresight. We didn't predict this CVE. The decision was made for ordinary strategic reasons — open-source consistency, license economics, tighter integration with our own dashboard. What the timing illustrates, I think, is the broader argument: when you treat a commercial control panel as the management plane for thousands of customer accounts, you've taken on a dependency whose risk profile you don't control. License cost is the visible part. Concentration risk and disclosure latency are the invisible part. They happen to point in the same direction.

Our specific postmortem on the CVE is in WAYSCLOUD-TR-2026-0016: vendor automatic patching was active before the observed exploitation attempts, and blocked attempts were logged. That's not because we were smarter than anyone else. It's because we had a patch pipeline that runs in hours rather than weeks, and a perimeter where 2087 isn't a port the internet talks to directly. The HestiaCP migration is a separate decision that happens to take us further in the same direction.

The general principle, for anyone building infrastructure right now: design so that a single vendor's failure doesn't cascade through your customer base. Distributed control planes. Smaller machines with smaller blast radius. Network-level compartmentalization. An operational model where "every management interface is on the public internet" is never the default answer. And open-source where you can — not because open source is automatically more secure (it isn't), but because the ability to read the code, fork the code, and patch the code yourself is the only durable form of independence from anyone else's disclosure timeline.


Sources

Primary technical analysis:

Telemetry and spread:

Official advisories:

Internal:


Errata

2026-05-10: An earlier version of this post stated that cPanel held "94% of the world's shared hosting control panel market," attributed to W3Techs. Both the figure and the attribution were incorrect. The 94% number originates with 6sense within a narrowly-defined "Web Hosting Control Panel" category that excludes Plesk; W3Techs detection-based data places Plesk as the leading panel at ~46% share, with cPanel lower; Datanyze places cPanel at ~22% commercial installed base. The post has been updated to use exposure metrics (1.5M internet-facing instances per Shodan, 650K per Shadowserver) and a more precise framing of cPanel's position in commercial Linux shared hosting. The underlying argument about concentration risk is unaffected by the correction.

The same revision corrected three smaller numerical claims: the exploit chain was described as "three HTTP requests" (it's four end-to-end per watchTowr; updated to "a handful"); the injected payload was described as "twelve lines" (it's six; corrected); and targeted MSPs were described as spanning "three continents" (Canada, US, and South Africa — two continents; corrected to name the countries directly).

Thanks to a reader who flagged the market-share figure for verification.