Back to blog

CVE-2026-41940: How Three Small Mistakes in cPanel Handed Attackers the Keys to 1.5 Million Servers

· · 12 min read
CVE-2026-41940: How Three Small Mistakes in cPanel Handed Attackers the Keys to 1.5 Million Servers

On April 28, 2026, cPanel issued an emergency security update for what it described in its release notes as "an issue with session loading and saving." That description aged quickly. Within 24 hours, watchTowr Labs researcher Sina Kheirkhah had published a full technical breakdown showing that the bug was a pre-authentication root takeover affecting every currently supported version of cPanel and WHM. The vulnerability was assigned CVE-2026-41940 with a CVSS score of 9.8 (Critical). And by then, attackers had already been exploiting it silently for roughly two months.

This article follows the same structure we use for all our CVE breakdowns: the first part is written for decision-makers who need to act fast; the second part is for engineers and security teams who want to understand what actually broke and why.

Part I. Executive Breakdown

What Happened

cPanel and WHM are the control panels that power a significant portion of the internet's shared hosting infrastructure. WHM gives hosting providers root-level control over the entire server. cPanel gives individual customers control over their own accounts. Both are served by a Perl daemon called cpsrvd.

CVE-2026-41940 is an authentication bypass that allows an unauthenticated attacker to inject arbitrary data into a server-side session file during the login flow, before any credential check takes place. By injecting a line like user=root into that file, the attacker's session is treated as a fully authenticated administrator session. No valid username. No valid password. No multi-factor authentication. Just a crafted HTTP request.

The root cause is a chain of three smaller mistakes that individually looked harmless but together opened a critical path to full server compromise:

  1. The Basic Authorization header handler in cpsrvd did not strip carriage return and line feed (\r\n) characters from user input.
  2. An attacker could craft a session cookie that omitted the expected prefix, which caused the encryption step applied to sensitive session fields to be silently skipped.
  3. The function responsible for sanitizing session data existed in the codebase but was not called by the login handler, only by callers that remembered to invoke it themselves. When those three conditions align, an attacker can write a session file that contains administrator-level attributes and then load it back to gain full WHM access.

Who Is Affected

Product Vulnerable Versions Patched Version
cPanel & WHM 110.0.x All versions up to 11.110.0.96 11.110.0.97
cPanel & WHM 118.0.x All versions up to 11.118.0.61 11.118.0.63
cPanel & WHM 126.0.x All versions up to 11.126.0.53 11.126.0.54
cPanel & WHM 132.0.x All versions up to 11.132.0.27 11.132.0.29
cPanel & WHM 134.0.x All versions up to 11.134.0.19 11.134.0.20
cPanel & WHM 136.0.x All versions up to 11.136.0.4 11.136.0.5
WP Squared 11.136.1.x All versions up to 11.136.1.6 11.136.1.7

Every version after v11.40 is affected, including all supported release tracks. A Shodan scan referenced by Rapid7 identified approximately 1.5 million internet-exposed cPanel instances at the time of disclosure, though the precise number that had applied the patch was unknown.

Why This Matters

The most important detail in this CVE is the timeline. Hosting provider KnownHost confirmed that exploitation in the wild was observed as early as February 23, 2026, roughly two months before cPanel released its patch on April 28. That means CVE-2026-41940 was a true zero-day for most of its active life. Attackers did not need a public proof-of-concept because the vulnerability is straightforward to discover if you spend enough time reading the session handling code.

CISA added CVE-2026-41940 to its Known Exploited Vulnerabilities (KEV) catalog on April 30, with a binding remediation deadline for federal agencies under BOD 22-01.

Successful exploitation of WHM means full control of the host server: all websites it serves, all databases, all email accounts, all DNS configuration, and all hosted credentials. On shared hosting, a compromise of the host is effectively a compromise of every tenant on that host.

Recommended Actions

  1. Patch immediately. Update to the patched version for your release track. If you rely on auto-update, verify it has actually run and that the new version is installed. Restart cpsrvd after updating by running /scripts/restartsrv_cpsrvd.
  2. If you cannot patch right now, block external access to cPanel and WHM ports: TCP/2082, TCP/2083, TCP/2086, TCP/2087, TCP/2095, TCP/2096, TCP/2077, TCP/2078. These interfaces should never be exposed to the public internet in the first place.
  3. Run cPanel's IOC detection script. cPanel published an indicator-of-compromise script that scans session files for signs of exploitation. Save it to your server and run it with /bin/bash ./ioc_checksessions_files.sh. Look for sessions with injected authentication timestamps or sessions that contain user=root set before authentication completed.
  4. Review your access logs. Inspect /usr/local/cpanel/logs/access_log and /var/log/ for login activity prior to April 28, 2026. Any authenticated WHM sessions from that window deserve investigation.
  5. Audit accounts and SSH keys. In WHM, review whether any accounts were created that you do not recognize. In cPanel, check FTP accounts, email accounts, and SSH authorized keys.
  6. Check your network exposure. Search your public IP ranges on Shodan to understand how many cPanel instances you have exposed and whether they appear in common targeting lists.

Part II. Technical Breakdown

How cPanel Session Files Work

cpsrvd is the Perl daemon that serves both cPanel (ports 2082/2083) and WHM (ports 2086/2087). When a login attempt arrives, cpsrvd creates a session file on disk before the credential check completes. That file lives under /var/cpanel/session/ and contains key-value pairs, one per line, using a simple key=value format separated by newlines.

A normal login failure looks like this. Send a bad password to the WHM login endpoint:

POST /login/?login_only=1 HTTP/1.1
Host: target:2087
Content-Type: application/x-www-form-urlencoded

user=root&pass=wrongpassword

The server responds with a 401 and sets a session cookie:

HTTP/1.1 401 Access Denied
Set-Cookie: whostmgrsession=%3aWg_mjzgt1hyfXefK%2c1bd3d4bf5ecbf83b660789ab0f3198fa; HttpOnly; path=/; secure

URL-decoding the cookie value gives :Wg_mjzgt1hyfXefK,1bd3d4bf5ecbf83b660789ab0f3198fa. This is a session name composed of two parts separated by a comma: an ob prefix and a session ID. The ob value is used as a secret for encoding sensitive fields like pass when the session is written to disk.

The Three-Flaw Chain

Flaw 1: The Basic Authorization header does not sanitize CRLF characters. cpsrvd also supports HTTP Basic Auth as a login method. The value from the Authorization header is decoded and used to populate the session. But unlike the form-based login path, this handler does not call filter_sessiondata before writing the session file. That function already existed in the codebase and was responsible for stripping \r, \n, =, and , from session values. The omission meant an attacker could inject these characters via the Authorization header and have them written verbatim into the session file.

An attacker sends a request like this (the exact encoding varies by exploitation approach, but the concept is the following):

Authorization: Basic <base64("root:fakepass\r\nuser=root\r\nhasauthtoken=1")>

When cpsrvd decodes the header and writes the session file, the injected \r\n characters act as line breaks, creating new key-value entries in the session file:

user=root
pass=fakepass
user=root
hasauthtoken=1

The session file parser reads all lines and populates a hash map. Later entries overwrite earlier ones. The result is a session that carries user=root and a flag indicating the session is already authenticated.

Flaw 2: Truncating the cookie disables session encryption. The ob prefix in the session cookie is extracted from the cookie value by looking for a , separator. If the attacker sends a cookie that omits the ,, the ob value comes back as undef. In the unpatched code, when ob is undefined, the conditional that initializes the Cpanel::Session::Encoder object is never reached, meaning the pass field is written in cleartext. This is not directly exploitable on its own, but it affects how the session is structured and contributes to the overall bypass.

The unpatched saveSession logic looked like this:

my $encoder = $ob && Cpanel::Session::Encoder->new( 'secret' => $ob );
local $session_ref->{'pass'} = $encoder->encode_data( $session_ref->{'pass'} )
  if $encoder && length $session_ref->{'pass'};

If $ob is falsy, $encoder is never initialized and the encoding step is silently skipped. The patched version handles this explicitly:

filter_sessiondata($session_ref);  # called unconditionally now
if ( length $session_ref->{'pass'} ) {
    if ( defined $ob && length $ob ) {
        my $encoder = Cpanel::Session::Encoder->new( 'secret' => $ob );
        $session_ref->{'pass'} = $encoder->encode_data( $session_ref->{'pass'} );
    }
    else {
        $session_ref->{'pass'} =
          'no-ob:' . Cpanel::Session::Encoder->hex_encode_only( $session_ref->{'pass'} );
    }
}

The patch moves filter_sessiondata inside saveSession itself, so it is called unconditionally regardless of which code path reaches the save operation.

Flaw 3: The sanitizer was the caller's responsibility. The filter_sessiondata function was defined in Session.pm and worked correctly when called. It stripped \r, \n, =, and , from all session field values, preventing any injected characters from creating additional lines in the session file. The problem was that calling it was left to each individual caller. The Basic Auth handler forgot to call it. That single omission was all the vulnerability required.

The Session File Anatomy

A written session file contains entries like the following:

user=someuser
pass=<encoded_password>
ip=1.2.3.4
service=whostmgrd
hasauthtoken=0

The session loader (Cpanel/Session/Load.pm) reads each line, splits on =, and builds a hash. When the attacker's injected session file contains:

user=root
pass=injectedvalue
user=root
hasauthtoken=1

The loader ends up with a hash where user is root and hasauthtoken is 1. When cpsrvd loads this session on the next request, the authentication check sees an already-authenticated root session and skips the password verification entirely.

Indicators of Compromise

cPanel's IOC detection script looks for three specific patterns in session files:

The Patch in Three Files

The fix touched three files inside the Cpanel Perl module tree:

File Change
Cpanel/Session.pm filter_sessiondata is now called unconditionally inside saveSession; explicit handling added for the no-ob case
Cpanel/Session/Load.pm Improved validation of session field values during load
Cpanel/Session/Encoder.pm New hex round-trip primitives used by the patched save path

The core lesson is not that a function was missing. It is that placing the responsibility for calling a security-critical sanitizer on individual callers is a design pattern that will fail eventually. The fix centralizes the call to make it impossible to forget.

Shodan Fingerprinting

Researchers querying Shodan for exposed cPanel instances using the http.title:"cPanel" or http.title:"WHM" filters found approximately 1.5 million results. Most of those results expose ports 2083 and 2087 directly to the internet, which is exactly the attack surface CVE-2026-41940 requires.

A simple check to see if an instance is still running a vulnerable version (for use only on systems you own or are authorized to test):

curl -sk https://target:2086/cpsessXXXX/json-api/version | python3 -m json.tool

The version returned can be compared against the patched version table above.

What This Means for Infrastructure Security

CVE-2026-41940 follows a pattern that appears across a wide range of infrastructure software: a security function exists, works correctly, but its use is optional. Nobody removes it. Nobody breaks it. A single developer on a single code path just does not call it, and that is enough.

The broader takeaway is not specific to cPanel. Any system where session data is written to disk based on unauthenticated input is a potential target for this class of attack. The attacker does not need to crack encryption or exploit memory corruption. They need to find one place where raw user input reaches a persistent data store before sanitization runs.

Some questions worth asking about your own infrastructure:

How Pragma Core Helps

The Pragma Core platform is built to surface exactly the class of vulnerability that enabled CVE-2026-41940 before it reaches production. Several capabilities are directly relevant:

SAST Scanning analyzes your source code for patterns where user-controlled input reaches file write operations or session stores without passing through a sanitization function. In the cPanel case, the missing filter_sessiondata call on the Basic Auth path would be flagged as a taint flow from HTTP input to a disk write with no sanitization step in between.

SCA / Dependency Tracker continuously monitors your software inventory against CVE feeds, including KEV additions from CISA. If you run cPanel-adjacent or cPanel-integrated systems, new critical CVEs surface in your dashboard the same day they are published, not when your next scheduled scan runs.

White-Box Pentest uses AI agents that read your actual codebase alongside dynamic testing, allowing them to trace the same kind of logic chain that watchTowr traced through Session.pm and Session/Load.pm. This is the difference between finding that a session endpoint exists and understanding whether the session file writer actually sanitizes its input.

DAST exercises live authentication flows against your running applications and specifically probes for CRLF injection vectors in header processing, form inputs, and cookie values, including patterns directly analogous to the CVE-2026-41940 attack chain. If you want to understand your exposure to this CVE or to similar session-handling vulnerabilities in your own codebase, reach out to the Pragma Core team.

References

Related posts
CVE-2026-74820: how an unsanitized ORDER BY clause turned ServiceNow's AI Platform into an unauthenticated database backdoor
Sep 18, 2026
CVE-2026-85978: how one path normalization mismatch turned Akana's admin console into unauthenticated remote code execution
Sep 9, 2026
CVE-2026-78174: how an unredacted session token in a diagnostic log turned a low-privileged WatchGuard Dimension admin into super admin
Sep 1, 2026

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →