[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-79926":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":15,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":17,"fork":17,"defaultBranch":18,"hasWiki":19,"hasPages":17,"topics":20,"createdAt":9,"pushedAt":9,"updatedAt":21,"readmeContent":22,"aiSummary":23,"trendingCount":14,"starSnapshotCount":14,"syncStatus":24,"lastSyncTime":25,"discoverSource":26},79926,"cpanel2shell-scanner","assetnote\u002Fcpanel2shell-scanner","assetnote","High fidelity scanner for CVE-2026-41940 (cPanel & WHM authentication bypass)",null,"Python",89,24,1,0,3,4.19,false,"master",true,[],"2026-06-12 02:03:55","# cpanel2shell-scanner\n\nA high-fidelity scanner for the cPanel\u002FWHM authentication bypass tracked as\nCVE-2026-41940. It identifies vulnerable hosts without producing the\nfalse-negatives common to public proofs-of-concept and detections, and without\ntriggering the account lockout and root-IP-allowlist mechanisms that interfere\nwith naive scanning.\n\nThe tool also bundles a separate, opt-in exploit chain for a same-family\nCalDAV path-traversal bug on `cpdavd` (ports 2079 plain \u002F 2080 TLS) —\n[CVE-2026-29205](https:\u002F\u002Fsupport.cpanel.net\u002Fhc\u002Fen-us\u002Farticles\u002F40437020299927-Security-CVE-2026-29205-cPanel-WHM-WP2-Security-Update-May-13-2026) —\nthat lets a remote attacker read arbitrary files **as root**\nonce a small SMTP-driven setup step succeeds. cPanel 11.134.0.26 fixes the\nunderlying RAII lifetime bug so the read now runs as the unprivileged account\nowner; the traversal itself still reaches `cpdavd` but cannot escalate beyond\nwhat that account can already read. The CalDAV chain is gated behind\n`--exploit` and is **off by default** because it sends real emails and reads\nfiles from confirmed targets — see the\n[Exploit mode (active)](#exploit-mode-active) section.\n\n## Why this scanner\n\nMost public detections for CVE-2026-41940 share three problems. This scanner\naddresses each of them.\n\nYou can read our blog post on this detection technique here: https:\u002F\u002Fslcyber.io\u002Fresearch-center\u002Fhigh-fidelity-check-for-the-cpanel-authentication-bypass-cve-2026-41940\u002F\n\n### It checks the proxy paths, not just the management ports\n\ncPanel's per-vhost Apache configuration installs a `ProxyPass` that forwards\n`\u002F___proxy_subdomain_whm` to `127.0.0.1:2086` and `\u002F___proxy_subdomain_cpanel`\nto `127.0.0.1:2080` regardless of the request's `Host` header. The\n`RewriteCond` only constrains the rewrite that maps the management subdomain\nonto the proxy path; the `ProxyPass` itself is unconditional. Hitting these\npaths on any vhost served by a cPanel-managed Apache reaches the same vulnerable\nbackend as the management ports.\n\nScanners that only probe ports 2082\u002F2083\u002F2086\u002F2087 will report a host as not\nvulnerable when those ports are firewalled, even though the bug is fully\nreachable through 443. This scanner probes 2087, 2083, and the two proxy paths\non 443 by default.\n\n### It does not get blocked by cphulkd or the root-IP allowlist\n\ncPanel ships `cphulkd`, which locks accounts out after a small number of failed\npassword attempts, and `authorized_whm_root_ips`, which restricts root logins\nto a configured list of source addresses. A scanner that exploits the bypass by\ntrying to inject a session for `root` will:\n\n- be silently ignored when the scanner's IP is not in the root allowlist,\n  producing a false negative; and\n- contribute failed-password events for whichever account it targets,\n  eventually locking that account out and preventing both detection and\n  legitimate logins.\n\nThis scanner avoids both issues on the WHM side by injecting `expired=1` into\nthe session payload under a randomly generated username. The session injection\nis verified by visiting the resulting `cpsessXXXX` URL and matching\n`msg_code:[expired_session]` in the response body, which is only present when\nthe injection succeeded. No real account is targeted, so no real account can be\nlocked out, and the root allowlist is irrelevant because no root login is\nattempted.\n\n### It uses a username wordlist where it has to\n\nThe cPanel daemon (`cpaneld`, ports 2083 and the `\u002F___proxy_subdomain_cpanel`\npath) requires the supplied username to correspond to an existing cPanel\naccount on disk (`-f \u002Fvar\u002Fcpanel\u002Fusers\u002F$user`). A username of `root` will never\nsatisfy this check because root is a system user, not a cPanel user. Detections\nthat try only `root` produce false negatives on this surface. This scanner uses\na configurable wordlist of common cPanel usernames against the cPanel surface\nand falls back to the random-username path on the WHM surface, which has no\nsuch restriction.\n\n## How the detection works\n\nFor each target the scanner performs the following steps per surface:\n\n1. Issue `GET \u002Flogin` and read the `Set-Cookie` header for either\n   `whostmgrsession` (WHM) or `cpsession` (cPanel). The cookie contains a\n   comma-separated session-name component.\n2. Issue `GET \u002F` with an `Authorization: Basic` header whose decoded value is\n   `\u003Cuser>:\\xff\\nexpired=1`. The trailing `\\nexpired=1` is the session-injection\n   payload. The session cookie from step 1 is replayed unmodified.\n3. Read the `Location` header from the response and extract the `cpsessXXXX`\n   token.\n4. Issue `GET \u002F\u003CcpsessXXXX>\u002F` with the original cookie and look for\n   `msg_code:[expired_session]` in the body. Its presence proves the session\n   injection succeeded and the host is vulnerable.\n\nOn WHM (port 2087 and the `\u002F___proxy_subdomain_whm` path on 443) the username\nis a random `u` followed by ten hex characters. On cPanel (port 2083 and the\n`\u002F___proxy_subdomain_cpanel` path on 443) the scanner walks its username\nwordlist and stops at the first match.\n\nBy default the scanner probes 2087, 2083, and 443 in that order and stops as\nsoon as any surface confirms vulnerability.\n\n## CalDAV path-traversal exploit (`--exploit`)\n\n> **[CVE-2026-29205](https:\u002F\u002Fsupport.cpanel.net\u002Fhc\u002Fen-us\u002Farticles\u002F40437020299927-Security-CVE-2026-29205-cPanel-WHM-WP2-Security-Update-May-13-2026)**\n> — cPanel\u002FWHM WP2 Security Update, May 13 2026. Fixed in cPanel\n> 11.134.0.26. The advisory tracks the same `cpdavd` privilege-drop\n> regression this exploit chain abuses.\n\nFull write-up of the bug and the exploitation chain:\nhttps:\u002F\u002Fslcyber.io\u002Fresearch-center\u002Fnew-age-of-collisions-reading-arbitrary-files-pre-auth-as-root-in-cpanel-cve-2026-29205\n\n`cpdavd` on ports 2079 (plain HTTP) and 2080 (TLS) trusts the\n`\u003Cprincipal>\u002F\u003Ccollection>\u002F...` path it builds when serving CalDAV\u002FCardDAV\nresources. By crafting a request whose path component encodes `..` segments\nand pointing it at a maildir folder whose on-disk name also encodes traversal\n(`x-attachment-1-y`), `cpdavd` can be coerced into reading **any file on disk\nas root, regardless of ownership or permissions** — including `\u002Fetc\u002Fshadow`,\n`\u002Fetc\u002Fpasswd`, and the per-user mail spools.\n\nThe defense-in-depth that was supposed to drop privileges to the account\nowner before the read silently failed: the `Cpanel::AccessIds::ReducedPrivileges`\nobject was constructed in void context, so its destructor restored root\nprivileges before the read ran. cPanel 11.134.0.26 binds the object to a\n`my $privs` lexical so it lives through the `-f` \u002F `stat` \u002F `open` \u002F `read`\nchain; on patched hosts the read therefore runs as the unprivileged account\nowner instead of root.\n\nThe vulnerable folder must exist on disk before the read works. cPanel\nauto-creates a folder named `.x-attachment-1-y` for the recipient\n`\u003Cuser>+x-attachment-1-y@\u003Cdomain>` the first time an email lands at that\nsub-address. The chain is therefore:\n\n1. Enumerate plausible recipient domains from the host's TLS certificate SANs.\n2. For each domain, derive candidate local-parts (the domain's first label,\n   plus a small wordlist of common mailbox prefixes such as `info`, `admin`,\n   `webmaster`).\n3. Open one SMTP session against a configured outbound relay (e.g. SendGrid)\n   and send `\u003Cprefix>+x-attachment-1-y@\u003Cdomain>` to each candidate. Accepted\n   `RCPT TO` responses are tracked.\n4. Wait through a retry ladder (5 s, 10 s, 20 s, 30 s) for the cPanel inbox\n   delivery to materialise the folder.\n5. For each accepted recipient, send the path-traversal `GET` against `cpdavd`\n   on ports 2080 (TLS) and 2079 (plain), under both the `\u002Fcalendar\u002F` and\n   `\u002Faddressbook\u002F` collection prefixes.\n\nA success returns the file's bytes; the finding records the email used, the\ncollection, the byte count, and the first 200 bytes as a preview.\n\nThis check is **disabled unless `--exploit` is passed**. When enabled it\nrequires a working outbound SMTP relay (see [Configuration file](#configuration-file)\nbelow) because the folder-creation step cannot be skipped.\n\n### Targeted vs sprayed exploitation\n\nThe exploit only works against **real virtual email accounts** configured\nunder cPanel's *Email Accounts* feature. Catch-all addresses do not work — a\ncatch-all routes via Exim's `system_aliases` router, never reaches the\n`dovecot_virtual_delivery` transport, and therefore never triggers the\n`lda_mailbox_autocreate` path that produces the `.x-attachment-1-y\u002F` folder.\n\nWhen you already know a valid virtual email on the target, pass it with\n`--email`:\n\n```\npython scanner.py --config scanner.ini --email admin@target.com target.com\n```\n\n`--email` skips the cert-SAN enumeration and the prefix wordlist entirely\nand sends exactly one message to the address you supplied. It may be\nrepeated to target multiple known accounts. The targeted path is much more\nreliable than the spray path.\n\nWithout `--email`, the scanner falls back to spraying ~15 common prefixes\n(`info`, `admin`, `webmaster`, etc.) per domain extracted from the host's\nTLS certificate. Assetnote's measurement on a sample of 200 cpdavd-exposed\nhosts was a **~10% spray hit rate**: a clean result from spray-mode is\nweak evidence that the host is patched, and re-running with `--email`\nagainst a real account is the only reliable way to confirm.\n\n## Configuration file\n\nExploit mode reads an INI file via `--config`:\n\n```\npython scanner.py --config scanner.ini --exploit example.com\n```\n\nCopy `scanner.ini.example` to `scanner.ini`, fill in the SMTP credentials, and\noptionally tune the CalDAV defaults. `scanner.ini` is in `.gitignore` so the\npopulated copy stays local. The SMTP password can also be supplied via the\n`SCANNER_SMTP_PASSWORD` environment variable, which takes precedence only when\nthe `password` field in the file is empty.\n\n## Installation\n\n```\npip install -r requirements.txt\n```\n\nPython 3.8 or later is required.\n\n## Usage\n\nSingle target:\n\n```\npython scanner.py example.com\n```\n\nMultiple targets via positional arguments:\n\n```\npython scanner.py host-a.example.com host-b.example.com:2083\n```\n\nA file of targets, one per line. Lines starting with `#` are ignored:\n\n```\npython scanner.py -f targets.txt\n```\n\nReading targets from stdin:\n\n```\ncat targets.txt | python scanner.py\n```\n\nA target may be either a hostname or `host:port`. When a port is specified the\nscanner only probes that port; otherwise it probes 2087, 2083, and 443.\n\n### Common options\n\n- `-u, --users` — comma-separated cPanel usernames to try on the cPanel\n  surface. Defaults to a small built-in list.\n- `-U, --users-file` — file with one cPanel username per line.\n- `-p, --ports` — comma-separated ports to probe when no port is specified on\n  the target. Defaults to `2087,2083,443`.\n- `-t, --threads` — per-target threads used to walk the username list against\n  the cPanel surface. Defaults to 10.\n- `-c, --concurrency` — number of targets scanned in parallel. Defaults to 20.\n- `-T, --timeout` — per-request timeout in seconds. Defaults to 15.\n- `-o, --output` — append vulnerable targets, one per line, to this file as\n  they are discovered.\n- `--json` — write a JSON Lines record per target to this file.\n- `-q, --quiet` — only print vulnerable targets on stdout. Connection failures\n  and clean targets are still recorded in `--json` and counted in the summary.\n- `--no-progress` — disable the progress bar.\n- `--exploit` — enable the CalDAV path-traversal chain. Disabled by default;\n  see [Exploit mode (active)](#exploit-mode-active) for the side effects this\n  flag unlocks.\n- `--config` — INI file with the SMTP relay credentials and CalDAV tunables.\n  See `scanner.ini.example`.\n- `--read-file` — file to exfiltrate when the CalDAV chain succeeds.\n  Overrides the value in the config file. Defaults to `\u002Fetc\u002Fshadow` — a\n  root-only file, so a successful read distinguishes pre-patch (returns\n  shadow contents) from post-patch (`open` denied, body empty → reported\n  NOT VULNERABLE). Use `--read-file \u002Fetc\u002Fpasswd` to test traversal\n  reachability without distinguishing patched\u002Funpatched.\n- `--caldav-only` — skip the 41940 check and only run the CalDAV chain.\n  Implies `--exploit`. Useful for re-running the chain against a target list\n  already known to be CalDAV-reachable.\n- `--email ADDR` — known virtual email account on the target. Skips cert SAN\n  enumeration and the spray wordlist; sends exactly one message to `ADDR` and\n  reads against that principal. May be repeated. Implies `--exploit`. See\n  [Targeted vs sprayed exploitation](#targeted-vs-sprayed-exploitation).\n- `-v, --verbose` — emit per-domain progress for the CalDAV chain\n  (cert SAN list, spray count, retry ladder).\n\n### Output\n\nOne line is written to stdout per *finding*, so a target running both checks\nwill print twice:\n\n```\n[!] host  cve-2026-41940    VULNERABLE (port 443)\n[!] host  caldav-traversal  VULNERABLE via admin@host (read 1842b from \u002Fetc\u002Fpasswd)\n[+] host  cve-2026-41940    NOT VULNERABLE\n[?] host  cve-2026-41940    CONNECTION FAILED\n```\n\nThe `--json` output is one record per target with a `findings` array:\n\n```json\n{\"target\": \"host\", \"status\": \"VULNERABLE\", \"findings\": [\n  {\"check\": \"cve-2026-41940\", \"status\": \"VULNERABLE\", \"detail\": {\"port\": 443}},\n  {\"check\": \"caldav-traversal\", \"status\": \"VULNERABLE\",\n   \"detail\": {\"email\": \"admin@host\", \"domain\": \"host\", \"collection\": \"calendar\",\n              \"file\": \"\u002Fetc\u002Fshadow\", \"bytes\": 1218, \"preview\": \"root:$6$...\"}}\n]}\n```\n\n`status` at the top level is the worst case across all findings.\n\nA summary line with totals is written to stderr at the end. The progress bar\nis rendered on stderr and is automatically suppressed when stderr is not a\nterminal.\n\nThe exit code is `0` if any target is vulnerable, `1` if every reachable target\nwas clean, and `2` if no target could be reached.\n\n### Examples\n\nScan a list of targets, write hits to a file, and stay quiet on stdout:\n\n```\npython scanner.py -f targets.txt -o vulnerable.txt -q\n```\n\nScan with a custom username list against the cPanel surface, increased\nparallelism, and JSON output for downstream processing:\n\n```\npython scanner.py -f targets.txt -U cpanel-users.txt -c 100 --json results.jsonl\n```\n\nProbe a non-default port set:\n\n```\npython scanner.py -p 2083,2087,8443 -f targets.txt\n```\n\nRun the CalDAV chain end-to-end against a single target with the email-spray\nfallback (requires a populated `scanner.ini`):\n\n```\npython scanner.py --config scanner.ini --exploit example.com\n```\n\nTargeted exploitation against a known virtual email — much higher hit rate\nthan spray:\n\n```\npython scanner.py --config scanner.ini --email admin@example.com example.com\n```\n\nRun only the CalDAV chain against a list of confirmed cpdavd hosts, dumping\n`\u002Fetc\u002Fpasswd` previews to JSON:\n\n```\npython scanner.py --config scanner.ini --caldav-only \\\n    -f cpdavd-hosts.txt --json caldav-results.jsonl\n```\n\n## Notes on safety\n\n### Default mode (safe)\n\nThe default `scanner.py \u003Ctarget>` invocation only runs the CVE-2026-41940\ndetector. It sends the requests required to confirm the session injection and\nnothing else. It does not log in as any real user, does not target the `root`\naccount, does not escalate to a shell, and does not accumulate failed-password\nevents against any valid account on a target system. The marker it matches\n(`msg_code:[expired_session]`) is generated by the application itself in\nresponse to the injected `expired=1` session field and is the same indicator\nthe upstream cPanel login page uses when a legitimately expired session is\nreplayed.\n\n### Exploit mode (active)\n\nPassing `--exploit` (or `--caldav-only`) unlocks the CalDAV path-traversal\nchain. This is no longer a detector — it is a working exploit. When enabled\nthe scanner will, for every target where domain enumeration succeeds:\n\n- open a real SMTP session against the relay configured in `scanner.ini` and\n  send one short message per candidate recipient (typically 10–15 per domain,\n  3 domains per target);\n- wait up to ~65 seconds per domain for delivery to materialise the malicious\n  maildir folder;\n- attempt to read the configured `--read-file` from every confirmed target.\n\nThe default file is `\u002Fetc\u002Fshadow`. A successful read returns the file's\nbytes; an empty body indicates either an unreachable target or a host where\nthe privilege-drop fix (cPanel 11.134.0.26, `my $privs = …`) is in place.\nDetection compares received body length to zero, not to the response's\nadvertised `Content-Length` (which is derived from `stat()` on the\nattacker-chosen path and will be populated even when the subsequent `open()`\nis denied).\n\nTo test traversal reachability independently of the privilege-drop fix —\ne.g. when you want to know whether `cpdavd` is reachable and the maildir\nprerequisite was satisfied on a patched host — re-run with\n`--read-file \u002Fetc\u002Fpasswd`. `\u002Fetc\u002Fpasswd` is world-readable so it returns\nbytes on both pre-patch and post-patch hosts; combining the two signals\n(`\u002Fetc\u002Fshadow` body present = pre-patch root read; `\u002Fetc\u002Fshadow` empty +\n`\u002Fetc\u002Fpasswd` present = traversal reachable but priv-drop fix applied)\nclassifies a host unambiguously.\n\nThe contents and a 200-byte preview are written to the JSONL output and\nprinted on stdout. Per-domain CalDAV exploitation can take a minute or\nmore — this is the retry ladder waiting for mail delivery, not a hang.\n\nOnly run `--exploit` against assets you own or have explicit written\nauthorisation to test. The SMTP traffic is logged by the configured relay and\nby every recipient mail system; the file reads are logged by `cpdavd`.\n","cpanel2shell-scanner 是一个针对 CVE-2026-41940（cPanel & WHM 认证绕过漏洞）的高精度扫描工具。该工具能够识别出存在漏洞的主机，避免了常见的误报问题，并且不会触发账户锁定和根IP白名单机制，从而避免干扰正常的扫描过程。此外，它还提供了一个可选的利用链，用于检测另一个同家族的 CalDAV 路径遍历漏洞（CVE-2026-29205），允许远程攻击者在成功执行一个小的SMTP驱动设置步骤后以root权限读取任意文件。此工具适用于需要对 cPanel\u002FWHM 服务器进行安全检查的场景，特别是那些关注高精度和低误报率的环境。",2,"2026-06-11 03:58:34","CREATED_QUERY"]