Share
## https://sploitus.com/exploit?id=44EB5F23-5722-5A31-9188-7E5749ABA7FF
# cpanel2shell-scanner

A high-fidelity scanner for the cPanel/WHM authentication bypass tracked as
CVE-2026-41940. It identifies vulnerable hosts without producing the
false-negatives common to public proofs-of-concept and detections, and without
triggering the account lockout and root-IP-allowlist mechanisms that interfere
with naive scanning.

## Why this scanner

Most public detections for CVE-2026-41940 share three problems. This scanner
addresses each of them.

You can read our blog post on this detection technique here: https://slcyber.io/research-center/high-fidelity-check-for-the-cpanel-authentication-bypass-cve-2026-41940/

### It checks the proxy paths, not just the management ports

cPanel's per-vhost Apache configuration installs a `ProxyPass` that forwards
`/___proxy_subdomain_whm` to `127.0.0.1:2086` and `/___proxy_subdomain_cpanel`
to `127.0.0.1:2080` regardless of the request's `Host` header. The
`RewriteCond` only constrains the rewrite that maps the management subdomain
onto the proxy path; the `ProxyPass` itself is unconditional. Hitting these
paths on any vhost served by a cPanel-managed Apache reaches the same vulnerable
backend as the management ports.

Scanners that only probe ports 2082/2083/2086/2087 will report a host as not
vulnerable when those ports are firewalled, even though the bug is fully
reachable through 443. This scanner probes 2087, 2083, and the two proxy paths
on 443 by default.

### It does not get blocked by cphulkd or the root-IP allowlist

cPanel ships `cphulkd`, which locks accounts out after a small number of failed
password attempts, and `authorized_whm_root_ips`, which restricts root logins
to a configured list of source addresses. A scanner that exploits the bypass by
trying to inject a session for `root` will:

- be silently ignored when the scanner's IP is not in the root allowlist,
  producing a false negative; and
- contribute failed-password events for whichever account it targets,
  eventually locking that account out and preventing both detection and
  legitimate logins.

This scanner avoids both issues on the WHM side by injecting `expired=1` into
the session payload under a randomly generated username. The session injection
is verified by visiting the resulting `cpsessXXXX` URL and matching
`msg_code:[expired_session]` in the response body, which is only present when
the injection succeeded. No real account is targeted, so no real account can be
locked out, and the root allowlist is irrelevant because no root login is
attempted.

### It uses a username wordlist where it has to

The cPanel daemon (`cpaneld`, ports 2083 and the `/___proxy_subdomain_cpanel`
path) requires the supplied username to correspond to an existing cPanel
account on disk (`-f /var/cpanel/users/$user`). A username of `root` will never
satisfy this check because root is a system user, not a cPanel user. Detections
that try only `root` produce false negatives on this surface. This scanner uses
a configurable wordlist of common cPanel usernames against the cPanel surface
and falls back to the random-username path on the WHM surface, which has no
such restriction.

## How the detection works

For each target the scanner performs the following steps per surface:

1. Issue `GET /login` and read the `Set-Cookie` header for either
   `whostmgrsession` (WHM) or `cpsession` (cPanel). The cookie contains a
   comma-separated session-name component.
2. Issue `GET /` with an `Authorization: Basic` header whose decoded value is
   `:\xff\nexpired=1`. The trailing `\nexpired=1` is the session-injection
   payload. The session cookie from step 1 is replayed unmodified.
3. Read the `Location` header from the response and extract the `cpsessXXXX`
   token.
4. Issue `GET //` with the original cookie and look for
   `msg_code:[expired_session]` in the body. Its presence proves the session
   injection succeeded and the host is vulnerable.

On WHM (port 2087 and the `/___proxy_subdomain_whm` path on 443) the username
is a random `u` followed by ten hex characters. On cPanel (port 2083 and the
`/___proxy_subdomain_cpanel` path on 443) the scanner walks its username
wordlist and stops at the first match.

By default the scanner probes 2087, 2083, and 443 in that order and stops as
soon as any surface confirms vulnerability.

## Installation

```
pip install -r requirements.txt
```

Python 3.8 or later is required.

## Usage

Single target:

```
python scanner.py example.com
```

Multiple targets via positional arguments:

```
python scanner.py host-a.example.com host-b.example.com:2083
```

A file of targets, one per line. Lines starting with `#` are ignored:

```
python scanner.py -f targets.txt
```

Reading targets from stdin:

```
cat targets.txt | python scanner.py
```

A target may be either a hostname or `host:port`. When a port is specified the
scanner only probes that port; otherwise it probes 2087, 2083, and 443.

### Common options

- `-u, --users` โ€” comma-separated cPanel usernames to try on the cPanel
  surface. Defaults to a small built-in list.
- `-U, --users-file` โ€” file with one cPanel username per line.
- `-p, --ports` โ€” comma-separated ports to probe when no port is specified on
  the target. Defaults to `2087,2083,443`.
- `-t, --threads` โ€” per-target threads used to walk the username list against
  the cPanel surface. Defaults to 10.
- `-c, --concurrency` โ€” number of targets scanned in parallel. Defaults to 20.
- `-T, --timeout` โ€” per-request timeout in seconds. Defaults to 15.
- `-o, --output` โ€” append vulnerable targets, one per line, to this file as
  they are discovered.
- `--json` โ€” write a JSON Lines record per target to this file.
- `-q, --quiet` โ€” only print vulnerable targets on stdout. Connection failures
  and clean targets are still recorded in `--json` and counted in the summary.
- `--no-progress` โ€” disable the progress bar.

### Output

For each target one line is written to stdout:

```
[!] host VULNERABLE (port 443)
[+] host NOT VULNERABLE
[?] host CONNECTION FAILED
```

A summary line with totals is written to stderr at the end. The progress bar
is rendered on stderr and is automatically suppressed when stderr is not a
terminal.

The exit code is `0` if any target is vulnerable, `1` if every reachable target
was clean, and `2` if no target could be reached.

### Examples

Scan a list of targets, write hits to a file, and stay quiet on stdout:

```
python scanner.py -f targets.txt -o vulnerable.txt -q
```

Scan with a custom username list against the cPanel surface, increased
parallelism, and JSON output for downstream processing:

```
python scanner.py -f targets.txt -U cpanel-users.txt -c 100 --json results.jsonl
```

Probe a non-default port set:

```
python scanner.py -p 2083,2087,8443 -f targets.txt
```

## Notes on safety

The scanner only sends the requests required to confirm the session injection.
It does not log in as any real user, does not target the `root` account, does
not escalate to a shell, and does not accumulate failed-password events
against any valid account on a target system. The marker it matches
(`msg_code:[expired_session]`) is generated by the application itself in
response to the injected `expired=1` session field and is the same indicator
the upstream cPanel login page uses when a legitimately expired session is
replayed.