rfxn/cpanel-sessionscribe

GitHub: rfxn/cpanel-sessionscribe

针对 cPanel/WHM 严重漏洞 CVE-2026-41940 的深度防御工具包,集成了漏洞探测、主动缓解、事件响应取证和底层逆向分析功能。

Stars: 7 | Forks: 0

# SessionScribe - CVE-2026-41940 **cPanel & WHM 中严重的未经身份验证 RCE 漏洞。** 只需四次 HTTP 请求,通过 CRLF 注入预身份验证会话的密码字段, 即可伪造 root 会话。 无需身份验证,无需前置条件,影响所有受支持的版本。 由 Sina Kheirkhah / [watchTowr Labs](https://labs.watchtowr.com/) 于 2026-04-28 披露。 rfxn.com 研究文章 [![CVE](https://img.shields.io/badge/CVE-2026--41940-d97757?labelColor=09090b)](https://support.cpanel.net/hc/en-us/articles/40073787579671) [![严重级别](https://img.shields.io/badge/severity-CRITICAL%20RCE-d44d4d?labelColor=09090b)](#priority-order) [![披露时间](https://img.shields.io/badge/disclosed-2026--04--28-22d3ee?labelColor=09090b)](https://support.cpanel.net/hc/en-us/articles/40073787579671) [![许可证](https://img.shields.io/badge/license-GPL--2.0-22d3ee?labelColor=09090b)](LICENSE) [工具](#tools) · [攻击链](#the-chain) · [验证](#verify-yourself) · [各工具说明](#each-tool) · [攻击链输出](#kill-chain-output-ioc-scan---full) · [集群化使用](#fleet-usage) · [受影响的版本](#affected-builds) · [优先级顺序](#priority-order) · [报告](#reporting) · [参考](#references)

四个请求伪造 root 会话。六个版本仍然没有就地补丁。
架构层面的修复不在于二进制文件中——而在于代理端点。

本仓库是面向运维人员侧的工具包:一个分阶段的缓解编排器 (适用于已打补丁和未打补丁的版本)、可立即生效的 ModSec 规则、 用于集群扫描的非破坏性远程探测、宿主机 IOC 扫描器, 以及位于这篇[研究文章](https://rfxn.com/research/cpanel-sessionscribe-cve-2026-41940)背后的补丁差异快照收集器。 ``` # 审计单个主机(只读) curl -fsSLO https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-mitigate.sh bash sessionscribe-mitigate.sh # 完整修复 bash sessionscribe-mitigate.sh --apply # fleet 汇总 - 每个主机一行 CSV(host, os, cpanel_version, ...) bash sessionscribe-mitigate.sh --csv ``` ## 工具 该工具包中包含五个组件。点击名称可跳转至其快速入门和参考。 - **[`sessionscribe-mitigate.sh`](#sessionscribe-mitigatesh---mitigation-orchestrator)** - 缓解编排器 *(运行在 cPanel 宿主机上)* - **[`modsec-sessionscribe.conf`](#modsec-sessionscribeconf---modsecurity-rule-pack)** - ModSecurity 规则包 *(位于 cpsrvd 前端的 Apache + mod_security2)* - **[`sessionscribe-remote-probe.sh`](#sessionscribe-remote-probesh---non-destructive-verdict-per-host)** - 非破坏性远程探测 *(任何支持 `curl` 的环境)* - **[`sessionscribe-ioc-scan.sh`](#sessionscribe-ioc-scansh---on-host-ioc-ladder--kill-chain)** - 宿主机 IOC 阶梯检查 + 攻击链 *(运行在 cPanel 宿主机上;`--full` 将内联运行检测与取证阶段)* - **[`sessionscribe-revsnap.sh`](#sessionscribe-revsnapsh---re-snapshot-collector)** - 按版本区分的逆向工程 (RE) 快照收集器 *(运行在 cPanel 宿主机上,在执行 `upcp` 前后使用)* 所有组件均保存在本仓库的 [GitHub](https://github.com/rfxn/cpanel-sessionscribe) 中,并且可以通过各章节快速入门中显示的原始 URL 直接使用 `curl` 获取。采用 GPL v2 许可证。 ## 攻击链 四次 HTTP 请求,无需身份验证,无需前置条件: ``` sequenceDiagram autonumber actor A as attacker participant C as cpsrvd participant S as session file A->>C: POST /login/?login_only=1 with user=root, pass=wrong C-->>A: Set-Cookie · whostmgrsession=NAME,OBHEX A->>C: GET / · Authorization Basic b64(root:x + CRLF payload) · Cookie minus OBHEX C->>S: writes pass=x, user=root, hasroot=1, ... (CRLFs land verbatim) C-->>A: HTTP 307 · Location /cpsess[10digits]/ A->>C: GET /scripts2/listaccts · cookie only C->>S: propagate raw to cache · forged keys now readable C-->>A: 401 token denied (side-effect already done) A->>C: GET /cpsess[token]/json-api/version C-->>A: 200 OK means VULN · 403 means SAFE ``` 判定结果是第四次请求的 HTTP 状态码。位于 `/var/cpanel/sessions/raw/` 的磁盘会话文件是唯一的事后取证证据。`sessionscribe-remote-probe.sh` 以非破坏性的方式运行此攻击链(使用带有金丝雀标记的会话,不进行更改状态的 API 调用,并主动注销);`sessionscribe-ioc-scan.sh` 会直接读取该证据。 ## 自行验证 在任何 Linux 主机上进行 60 秒冒烟测试(无需 cPanel): ``` curl -fsSLO https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-mitigate.sh bash sessionscribe-mitigate.sh --list-phases # surface the phase API bash sessionscribe-mitigate.sh --check # safe read-only audit echo "exit=$?" # 0 on a non-cPanel host ``` 编排器会检测到非 cPanel 主机并干净地退出——证明了其幂等性而无需实验环境。存在 `/var/cpanel/` 目录的主机将运行完整的审计流程。 ## 问题溯源 安全版本发布后的第一个有用问题是*改变了什么*。对于 cPanel 来说,这比听起来要困难得多。`cpsrvd` 是一对剥离过的 ELF 二进制文件(启动器/负载),其 URL 路由、登录表单解析和 token 验证分布其中;`/usr/local/cpanel/Cpanel/` 和 `Whostmgr/` 下的 Perl 树承载着高级处理程序,但对于此类 bug,实际的修复面是经过编译的。官方并没有开放源代码发布。要进行差异比对,您必须捕获升级前后的二进制文件,并从字符串、dynsym 和反汇编代码向外进行推理。 这个收集工具就是 `sessionscribe-revsnap.sh`。它会为每个版本生成一个独立的 tarball 包,以便您可以跨版本比较二进制文件、字符串和 Perl 源代码。从中可以清晰地推导出利用原语。 该原语是**两个不对称性的组合**: 1. **`filter_sessiondata` 并未应用于所有写入路径。** `Cpanel::Session::create()`(即 `/login/` 表单路径)会调用 `filter_sessiondata()`,该函数会在字符串值写入磁盘之前剥离其中的 CR/LF。而 `Cpanel::Session::saveSession()`(当 `Authorization: Basic` 请求到达现有会话时使用的路径)则不会。任何通过 `saveSession()` 写入的内容都会原样落盘。 2. **编码器在缺失 `ob_part` 时发生短路。** `whostmgrsession` cookie 的规范格式为 `:NAME,OBHEX`。OBHEX 尾部为 `pass` 字段的编码器提供种子。`get_ob_part()` 通过正则表达式 `s/,([0-9a-f]{1,64})$//` 提取它。有五种 cookie 格式无法通过此正则表达式(无逗号、尾部逗号、非十六进制尾部、大写十六进制、十六进制尾部长度 >64 字符)。当匹配失败时,`$ob` 保持未定义状态,并且 `my $encoder = $ob && Encoder->new(...)` 发生短路——`$encoder` 为假值,下一行的 `$encoder->encode_data` 永远不会执行,`pass` 被原样写入。 组合利用:在跳过编码器且 `saveSession()` 不进行过滤的情况下,通过 `Authorization: Basic` 提供的密码会逐字符原样写入磁盘上的会话文件中。密码中的 CR/LF 会将单行 `pass=` 拆分为多行 `key=value`。cpsrvd 在将其读回时,会将其视为规范的会话属性。设置 `successful_internal_auth_with_timestamp`、`user=root` 和 `hasroot=1`,你就伪造了一个已登录的 root 会话。 供应商公告和 watchTowr 的文章详细记录了获取由此产生的 `cpsess` token 的请求链。 该补丁在缺失 ob_part 时对整个 `pass` 值进行十六进制编码(`pass=no-ob:`),并在读取端添加了配套的 `no-ob:` 解码分支。CR 和 LF 被转换为 ASCII 十六进制,该值不再能被分割成独立的 `key=value` 行,并且这种不变性被编码在数据本身中,而不是依赖于单一的函数调用。 完整的逆向工程演练——包括身份验证字符串的差异、134 个版本字节级相同字符串的问题,以及我们对相邻身份注入问题的发现——均包含在这篇[研究文章](https://rfxn.com/research/cpanel-sessionscribe-cve-2026-41940)中。 ## 各工具说明 ### `sessionscribe-mitigate.sh` - 缓解编排器 ``` curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-mitigate.sh | bash ``` **常见用法:** ``` # 只读审计(默认) bash sessionscribe-mitigate.sh # 完整修复 - 幂等,在健康主机上重新运行为 no-op bash sessionscribe-mitigate.sh --apply # 缩小范围 bash sessionscribe-mitigate.sh --apply --only modsec --probe bash sessionscribe-mitigate.sh --only patch,preflight # pre-upcp gate # fleet 汇总 bash sessionscribe-mitigate.sh --jsonl > host.jsonl bash sessionscribe-mitigate.sh --csv > host.csv ```
阶段参考 + 完整的 --help (点击展开) | 阶段 | 作用 | |---|---| | `patch` | 比对 `cpanel -V` 与已发布的修补版本列表(包括 EL6 11.86.0.41、EL6/CL6 110.0.103、124 版、以及 WP² 136.1.7) | | `preflight` | 移除 `/etc/yum.repos.d/threatdown.repo`;确保安装了 `epel-release`;禁用损坏的非基础仓库,防止 `upcp` 在运行中途崩溃 | | `upcp` | 如果未打补丁,则启动 `/scripts/upcp --force --bg` | | `proxysub` | 启用 `proxysubdomains` + 新账户变体;重建 httpd 配置 | | `csf` / `apf` / `runfw` | 从 `TCP_IN`/`TCP6_IN`/`IG_TCP_CPORTS` 中剥离 cpsrvd 端口 (2082/2083/2086/2087/2095/2096);验证当前的 iptables INPUT 链 | | `apache` | 检查 `httpd` 是否正在运行以及是否加载了 `security2_module` | | `modsec` | 检查 `modsec2.user.conf` 是否包含规则 `1500030` + `1500031`;如缺失则进行部署(带时间戳的备份、`httpd -t` 验证、平滑重载) | | `probe` (选填) | 针对 `127.0.0.1` 运行 `sessionscribe-remote-probe.sh`,以实际确认请求是否被拦截 | 在触碰任何文件之前,所有修改操作都会在 `/var/cpanel/sessionscribe-mitigation/` 下写入带有时间戳的备份。即使当前不可达,CentOS / Alma / Rocky 的 base/appstream/extras/updates/powertools 仓库也*绝不会*被 preflight 扫描禁用。 ``` sessionscribe-mitigate.sh v0.2.1 Defense-in-depth active mitigation for CVE-2026-41940 (SessionScribe). USAGE sessionscribe-mitigate.sh [MODE] [PHASE-SELECTION] [OUTPUT] [MISC] Read-only by default (--check). Use --apply to mutate state. All enabled phases run in order unless restricted via --only or excluded via --no-PHASE. Idempotent: re-running on a healthy host is a no-op. MODES --check Read-only audit (default). No state changes. --apply Execute remediations. Requires root. --dry-run Alias for --check. PHASE SELECTION --only LIST Run only the named phases (CSV, or "all"). Phases: patch,preflight,upcp,proxysub,csf,apf,runfw,apache,modsec,probe --no-PHASE Skip a phase. Per-phase opt-outs: --no-patch --no-preflight --no-upcp --no-proxysub --no-csf --no-apf --no-runfw --no-apache --no-modsec --no-fw Shorthand for --no-csf --no-apf --no-runfw. --probe Enable the optional probe phase (opt-in). Runs sessionscribe-remote-probe.sh against 127.0.0.1:2087; expects SAFE/blocked verdict. --list-phases Print phase IDs + descriptions, then exit. OUTPUT (mutually exclusive on stdout - last flag wins) (default) ANSI sectioned report on stderr. --json Single JSON envelope on stdout. --jsonl Stream one JSON signal per line on stdout. Every line carries host, os, cpanel_version, ts, tool_version, mode, phase, severity, key, note. --csv Single CSV summary row on stdout (header + one data row). One row per host - designed for fleet roll-up via cat *.csv | awk ... -o, --output FILE Write final JSON envelope (or CSV row if --csv is set) to FILE. MISC --quiet Suppress sectioned report. Auto-set by --jsonl/--csv. --no-color Disable ANSI color. NO_COLOR=1 env also honored. --backup-root DIR Backup directory for any mutation (default: /var/cpanel/sessionscribe-mitigation). --yes, -y Non-interactive; assume yes (no prompts). -h, --help Show this help. EXIT CODES 0 clean - patched + posture ok, no action needed 1 remediation applied successfully (--apply made changes) 2 manual intervention required (warns in --check, or fail in --apply) 3 tool error (bad args, missing dependencies, not root for --apply) ```
### `modsec-sessionscribe.conf` - ModSecurity 规则包 ``` curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/modsec-sessionscribe.conf | sudo tee /etc/apache2/conf.d/modsec/modsec2.user.conf >/dev/null sudo apachectl -t && sudo /usr/local/cpanel/scripts/restartsrv_httpd ``` **常见用法:** ``` # 全新安装 - modsec2.user.conf 默认为空,替换它 curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/modsec-sessionscribe.conf \ | sudo tee /etc/apache2/conf.d/modsec/modsec2.user.conf >/dev/null # 追加到现有的 user.conf 而不是替换 curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/modsec-sessionscribe.conf -o /tmp/ss.conf sed -n '/^# === RULES ===/,$p' /tmp/ss.conf \ | sudo tee -a /etc/apache2/conf.d/modsec/modsec2.user.conf # 编辑顶部的 @ipMatch 信任列表,然后验证 + 重新加载 sudo $EDITOR /etc/apache2/conf.d/modsec/modsec2.user.conf sudo apachectl -t sudo /usr/local/cpanel/scripts/restartsrv_httpd ```
规则参考 + 部署说明 (点击展开) | 规则 | 覆盖面 | 动作 | |---|---|---| | `1500030` | `Authorization: Basic` 解码后载荷中的 CRLF | 拒绝,适用于所有来源和所有路径 | | `1500031` | 缺少有效 `,OBHEX` 后缀的 `whostmgrsession` cookie | 拒绝(纵深防御) | | `1500010` | 针对路径 `/json-api/`、`/execute/`、`/acctxfer*/` 的 `Authorization: WHM` | 当来源不在信任列表中时拒绝 | | `1500020` | 针对 WebSocket 调度家族的 `Authorization: WHM` | 当来源不在信任列表中时拒绝 | | `1500021` | 针对 SSE 调度路径的 `Authorization: WHM` | 当来源不在信任列表中时拒绝 | 保留的 ID 范围是 `1500000–1500099`。每次拒绝都在阶段 1 执行——请求永远不会到达 body 检查器。规则 1500030 会对 `Authorization: Basic` 载荷进行 base64 解码,并在解码后的字节中检测到 CR/LF 时予以拒绝;合法的 Basic-auth 值解码后不会包含换行符,因此它没有信任列表绕过的问题。WHM-token 规则使用 `@ipMatch` 匹配由运维人员定义的信任列表——请在部署前编辑文件顶部的 CIDR。
### `sessionscribe-remote-probe.sh` - 每台主机的非破坏性判定 ``` curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-remote-probe.sh | bash -s -- --target 1.2.3.4 ``` **常见用法:** ``` # 单主机,默认 WHM-SSL 端口 bash sessionscribe-remote-probe.sh --target 1.2.3.4 # Apache 代理测试 - 通过 443 + 80 端口的 whm./cpanel./webmail.example.com bash sessionscribe-remote-probe.sh --target 1.2.3.4 --proxy example.com # fleet - 静默,遇到任何 VULN 以退出码 2 退出 bash sessionscribe-remote-probe.sh --target 1.2.3.4 --quiet --no-color # fleet - 跨多个目标的 CSV bash sessionscribe-remote-probe.sh --csv \ $(awk '{print "--target "$1}' fleet.txt) > fleet.csv # 快速范围界定 - 仅 banner 指纹,不生成 session bash sessionscribe-remote-probe.sh --target 1.2.3.4 --fingerprint-only # 在运行后清理目标上的 canary session bash sessionscribe-remote-probe.sh --cleanup ```
检测链 + 完整的 --help (点击展开) 探测脚本以非破坏性的方式运行四阶段链:创建预身份验证会话 (preauth) → CRLF 注入 → 传播 raw 到 cache → 通过 `/json-api/version` 验证,然后主动注销。决定判定结果的是第四阶段的 HTTP 状态码:`200` 或带有许可证正文的 `5xx`,表示 **VULN**(易受攻击);`401` 或 `403` 则表示 **SAFE**(安全)。每个测试会话都标记有 `nxesec_canary_` 属性,以便进行取证清理,并且**不会发出任何更改状态的 API 调用**。伪造的会话在第 3 阶段和第 5 阶段注销之间拥有约 1-3 秒的 root 等效权限——完整模型请参见脚本头。 ``` sessionscribe-remote-probe.sh v1.2.2 - detection probe for CVE-2026-41940 (SessionScribe) Usage: sessionscribe-remote-probe.sh --target HOST [--port PORT] [--scheme https|http] [--host-header NAME] sessionscribe-remote-probe.sh --target HOST --proxy DOMAIN sessionscribe-remote-probe.sh --target HOST --all sessionscribe-remote-probe.sh --target HOST1 --target HOST2 ... cat hosts.txt | sessionscribe-remote-probe.sh -- # batch via stdin Targeting: --target HOST IP, hostname, or [::1] for IPv6 (repeatable) --port PORT Direct cpsrvd port (default WHM ports if not set) --scheme https|http Default https --host-header NAME Override Host: header --proxy DOMAIN Test {whm,cpanel,webmail}.DOMAIN via 443 + 80 --all Exhaustive sweep - all 6 cpsrvd direct ports (cPanel/Webmail probes are informational only; see "Known limitation" in the script header) --auto-host-discover Pre-probe /openid_connect/cpanelid for canonical Host Output modes (mutually exclusive - last one wins): (default) Pretty per-probe output + summary -q | --quiet Only print [VULN] hits and the final verdict line --oneline One verdict line per target ("HOST: VULN n=2") --csv CSV header + one row per probe --json Structured JSON with probe results + per-target rollup --no-color Disable ANSI color (also honored if env NO_COLOR=1) --no-progress Suppress progress lines on multi-target runs --no-verify Stage-2-only mode (v1 heuristic - NOTE: produces FALSE POSITIVES on patched hosts). --fingerprint-only Stage 0 only - harvest cpsrvd build banner + cPanel_magic_revision and derive verdict from the embedded patch-boundary table. Side-effect-free (no session minted). Banner-only verdicts are LOWER CONFIDENCE than the full chain; use for fast scoping at fleet scale. --cleanup Print the local cleanup command (matches all past probe canaries: nxesec_canary_*) and exit. No probing performed. Tuning: --timeout N Per-request timeout seconds (default 10) --connect-timeout N TCP connect timeout (default 5) Exit codes: 0 no vulnerable targets found 1 inconclusive results only (no VULN, but at least one INCONCLUSIVE) 2 one or more VULN targets found Detection mechanism (full chain - default): Stage 0 GET /login/?login_only=1 passive fingerprint, no session Stage 1 POST /login/?login_only=1 mint preauth cookie Stage 2 GET / + Authorization Basic CRLF payload + ob-stripped cookie Stage 3 GET /scripts2/listaccts cookie only, propagate raw→cache Stage 4 GET /cpsess.../json-api/version → 200=VULN, 5xx+License=VULN, 401/403=SAFE Stage 5 GET /cpsess.../logout + GET /logout - best-effort invalidate ```
### `sessionscribe-ioc-scan.sh` - 宿主机 IOC 阶梯检查 + 攻击链 ``` curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-ioc-scan.sh | bash ``` **常见用法:** ``` # 默认分类(仅检测 — 快速,fleet 扫描) bash sessionscribe-ioc-scan.sh # 内联完整 kill-chain 重构(检测 + 取证阶段) bash sessionscribe-ioc-scan.sh --full # 完整 kill-chain + intake bundle 提交 bash sessionscribe-ioc-scan.sh --full --upload # 对已保存的 envelope 重放取证阶段(重新渲染而不重新扫描) bash sessionscribe-ioc-scan.sh --replay /var/cpanel/sessionscribe-ioc/.json bash sessionscribe-ioc-scan.sh --replay /root/.ic5790-forensic/ bash sessionscribe-ioc-scan.sh --replay /root/.ic5790-forensic/.tgz # 用于 SIEM 摄取的 JSONL bash sessionscribe-ioc-scan.sh --jsonl --quiet > host.jsonl # 用于 fleet 汇总的 CSV 摘要 bash sessionscribe-ioc-scan.sh --csv --quiet > host.csv # 仅主机 IOC - 定期补丁后扫描,过去 7 天 bash sessionscribe-ioc-scan.sh --ioc-only --since 7 # 对提取的 snapshot tarball 进行离线取证 bash sessionscribe-ioc-scan.sh \ --root /tmp/cpanel-122.0.17/usr/local/cpanel \ --version-string '11.122.0.17' \ --cpsrvd-path /tmp/cpanel-122.0.17/usr/local/cpanel/cpsrvd ```
检查项参考 + 判定维度 + 完整的 --help (点击展开) | 检查项 | 作用 | |---|---| | `version` | 比对 `cpanel -V` 与已发布的修补版本列表——驱动 `code_verdict` | | `static-pattern` | 在 `Cpanel/Session/*.pm` 中 grep 查找补丁后的哨兵模式(`no-ob:` 解码分支) | | `cpsrvd-fingerprint` | 对照已修补版本的签名检查 cpsrvd 二进制文件 | | `access-log` | 检查 Apache + cpsrvd 日志中是否存在利用流量特征(使用 `--no-logs` 跳过) | | `session-store` | 遍历 `/var/cpanel/sessions/raw/`:官方 IOC + 四路共现 + 伪造时间戳启发式分析(使用 `--no-sessions` 跳过) | | `destruction` | 模式 A–G 探测:`/root/sshd` 加密器、mysql 清除、BTC 索引、`nuclear.x86`、`sptadm` 分销商、`__S_MARK__` 收集器、可疑 SSH 密钥(使用 `--no-destruction-iocs` 跳过) | | `probe` (选填) | 向 `127.0.0.1:2087` 发起单个标记 GET 请求——确认 cpsrvd 有响应。**不会**尝试绕过 | 两个判定维度独立报告。**`code_verdict`**(`PATCHED` / `VULNERABLE` / `INCONCLUSIVE`)来源于版本、Perl 源码模式和二进制指纹。**`host_verdict`**(`CLEAN` / `SUSPICIOUS` / `COMPROMISED`)来源于会话文件 IOC 阶梯、访问日志扫描和模式 A–G 破坏性探测。由远程探测标记的 `nxesec_canary_` 会话被归类为 `PROBE_ARTIFACT`,并且不会升级至 `COMPROMISED`。 退出代码(优先级最高的生效,宿主机状态覆盖代码状态): | 退出码 | 代码状态 | 宿主机状态 | 分类处理动作 | |---|---|---|---| | 0 | CLEAN/PATCHED | CLEAN | 无 | | 1 | VULNERABLE | (任意) | 为 cpsrvd 打补丁 | | 2 | INCONCLUSIVE | (任意) | 人工复查代码状态(也包括工具错误——参数错误、缺少依赖) | | 3 | (任意) | SUSPICIOUS | 复查会话/访问日志 | | 4 | (任意) | COMPROMISED | 完整的应急响应 (IR);打包并上传 | 如果先前的利用在磁盘上留下了 IOC,已打补丁的主机仍可能以退出代码 `4` 退出。工具错误(参数错误、缺少依赖、无法读取的回放路径)将以退出代码 `2` 报告,而不是 `3`——退出代码 `3` 专用于 SUSPICIOUS 宿主机状态分配。自 v2.2.0 起,当 `ioc_review > 0` 时,ioc-scan 工具将报告 SUSPICIOUS(警告级 IOC:包括 `ioc_failed_exploit_attempt`、仅侦察的攻击者 IP 流量以及异常的 root 会话)。 默认情况下,运行账本会写入 `/var/cpanel/sessionscribe-ioc/`(使用 `--no-ledger` 禁用)。`--full` 会在同一进程内联运行取证阶段。`--chain-forensic`、`--chain-on-critical`、`--chain-upload` 保留为 v1.x 向后兼容的别名(映射为带有门控标志的 `--full`)。 ``` Usage: bash sessionscribe-ioc-scan.sh [OPTIONS] Scan options: --probe Send a single marker GET to 127.0.0.1:2087 (does not attempt the bypass - confirms cpsrvd is responsive and access logs are flowing). --no-logs Skip access-log IOC scan. --no-sessions Skip session-store IOC + anomaly scan. --no-destruction-iocs Skip destruction-stage probes (Patterns A-G: /root/sshd encryptor, mysql-wipe, BTC index, nuclear.x86, sptadm reseller, __S_MARK__ harvester, suspect SSH keys). Use for the original-shape ioc-scan triage when only session/log signals are wanted. --ioc-only Run only the host-state IOC scans (logs + sessions + destruction probes + optional marker probe). Skip version, static-pattern, and cpsrvd-binary code-state checks. The code_verdict is reported as SKIPPED; the exit code reflects host_verdict only. Useful for periodic post-patch sweeps. --exclude-ip CIDR Suppress attacker-IP cross-ref hits for this address (single IP only - no CIDR mask matching). Repeatable. Use for operator scan boxes / known-good IR sources. --since DAYS Limit log + session-anomaly scans to last N days. Default: no filter (scan all retained data). Vendor session IOCs (token-injection / preauth- extauth / tfa / multiline-pass) always scan the full /var/cpanel/sessions/raw/ regardless. Snapshot-testing overrides (offline forensics on extracted tarballs): --root DIR Override /usr/local/cpanel. --version-string S Override `cpanel -V` output. --cpsrvd-path P Override cpsrvd binary path. Output: -o, --output FILE Write structured output to FILE. Format follows the streaming flag in effect: CSV when --csv is set, JSON otherwise (default). --jsonl Stream JSONL on stdout (one signal per line, each prefixed with host= for fleet aggregation). Suppresses sectioned report. --csv Stream per-host summary CSV on stdout (one header row + one data row). Designed for fleet roll-up: pipe many hosts through `awk 'NR==1 || FNR>1'` or import into SQL/Excel. Mutually exclusive with --jsonl. Suppresses sectioned report. --quiet Suppress sectioned report. --no-color Disable ANSI color codes. Run ledger (default ON): --no-ledger Skip the /var/cpanel/sessionscribe-ioc/ run ledger. Use on hosts where you must not leave residue. --ledger-dir DIR Override default ledger directory (/var/cpanel/sessionscribe-ioc/). --syslog Emit a one-line summary via logger -t sessionscribe-ioc -p auth.notice on completion. Forensic modes (v2.0.0+): --full Run detection then forensic phases inline: defense / offense / reconcile / kill-chain / bundle. Writes envelope before forensic phases so --full and --replay share the same read path. --replay PATH Skip detection; replay forensic phases against a saved envelope (.json), bundle directory, or bundle tarball (.tgz). PATH resolution: 1. .json file → read directly 2. directory → scan for ioc-scan-envelope.json 3. .tgz/.tar.gz → extract to tmpdir, scan --no-bundle Skip artifact tarball capture (use in --full or --replay mode on Pattern A hosts or for fast kill-chain re-render). --chain-forensic Back-compat alias for --full (no host-verdict gate). --chain-on-critical Back-compat alias for --full with CHAIN_ON_CRITICAL=1 (skips forensic if HOST_VERDICT != COMPROMISED). --chain-upload Back-compat alias for --full --upload. Misc: --timeout N Probe timeout in seconds (default 8). -h, --help Show this help. Exit codes: 0=PATCHED+CLEAN, 1=VULNERABLE, 2=INCONCLUSIVE (also: tool error - bad args, missing deps), 3=SUSPICIOUS (host-state: ioc_review > 0), 4=COMPROMISED (host-state: ioc_critical > 0; overrides 0/1/2/3). ```
### 攻击链输出 (`ioc-scan --full`) `--full` 会先运行检测,然后内联运行取证阶段(防御时间线、攻击摄取、协调、攻击链渲染、打包)。在已知被攻陷的主机上,渲染器会将每个 IOC 与防御激活时间进行整理比对,并将其分类为 **PRE-DEFENSE**、**POST-DEFENSE**、**POST-PARTIAL** 或 **UNDEFENDED**,然后附带判定和防御延迟摘要进行总结。 示例(已脱敏,取自真实实验主机被攻击后的数据): ``` +-- CVE-2026-41940 / IC-5790 -------------------------------------------- | host cpanel.example.com () | cpanel unknown os unknown | verdict COMPROMISED score 315 ioc-scan v2.4.1 | defenses patch x absent modsec + up csf + clean mitigate + ran +------------------------------------------------------------------------ | -- PRE-DEFENSE (32 events) -- | 2026-03-25T09:43:19Z ! pattern X ioc_attacker_ip_2xx_on_cpsess 57 hit(s) (last 90d) from IC-5790 IPs returned 2xx on /cpsess/ paths - real exploitation | 2026-04-28T14:35:56Z ! pattern X ioc_cve_2026_41940_crlf_access_chain 15 CRLF-bypass chain(s) — POST /login 401 then GET /cpsess 2xx as root within 2s | 2026-04-28T16:38:45Z ! pattern E ioc_pattern_e_websocket_shell_hits 45 external IP(s) reached /cpsess*/websocket/Shell with 2xx (dim: 24x200:1,24x120:2,24x80:42) | 2026-04-29T08:41:22Z ! pattern F ioc_pattern_f_smark_envelope __S_MARK__/__E_MARK__ harvester envelope in /root/.bash_history | 2026-04-29T16:41:24Z ! pattern A ioc_pattern_a_ransom_readme /home/user1/README.md | 2026-04-29T16:41:38Z ! pattern A ioc_pattern_a_ransom_readme /home/user2/README.md | 2026-04-29T16:41:55Z ! pattern A ioc_pattern_a_ransom_readme /home/user3/README.md | 2026-04-29T16:41:56Z ! pattern A ioc_pattern_a_ransom_readme /home/user4/README.md | … (22 more Pattern A ransom_readme events across customer homedirs) | 2026-04-29T16:42:09Z ! pattern A ioc_pattern_a_sorry_files_present 608 .sorry-encrypted files present | 2026-04-29T17:52:58Z ! pattern D ioc_pattern_d_acctlog_encrypted /var/cpanel/accounting.log.sorry | 2026-04-29T17:53:37Z ! pattern A ioc_pattern_a_evidence_targeted 608 .sorry files under /var/log + /var/cpanel | -- DEFENSES -- | 2026-04-29T23:48:21Z + DEFENSE mitigate_first sessionscribe-mitigate.sh first run | 2026-04-29T23:48:21Z + DEFENSE csf csf.conf cpsrvd ports stripped | 2026-04-29T23:48:46Z + DEFENSE modsec modsec rule 1500030 installed | 2026-04-30T19:12:56Z + DEFENSE mitigate_last sessionscribe-mitigate.sh last run | -- POST-PARTIAL (1 event) -- | 2026-04-30T12:23:42Z ! pattern E ioc_pattern_e_handoff_burst_present 3 distinct external IPs each minted cpsess + reached websocket Shell within 15-min window | HEADLINE | verdict COMPROMISED (score 315) | defense lag 37d 9h LATE (first IOC 2026-03-25T09:43:19Z, defense up 37d 9h later) | attackers — (filesystem-only IOCs) counters defenses=4 iocs=33 pre=32 undef=0 post=1 attackers=0 · forensic_summary forensic reconstruction: COMPROMISED_PRE_DEFENSE ```
阶段 + 判定 + 打包结构 (点击展开) | 阶段 | 作用 | |---|---| | `defense` | 提取已落地的每层防御的时间戳:cpanel 补丁(`Load.pm` 修改时间)、cpsrvd 补丁后重启、`sessionscribe-mitigate.sh` 运行记录、ModSec 规则 1500030/1500031 安装时间、CSF/APF cpsrvd 端口关闭时间、proxysub 启用时间、`upcp` 摘要日志 | | `offense` | 提取每个观察到的入侵指标的时间戳:伪造会话 (模式 X)、`sptadm` 分销商 / `WHM_FullRoot` token (模式 D)、websocket Shell + Fileman API 数据窃取 (模式 E)、自动化收集器 shell 封装 (模式 F)、SSH 密钥持久化 (模式 G)、`.sorry` 加密器 + `/root/sshd` (模式 A)、BTC 勒索投递 + `/var/lib/mysql/mysql` 清除 (模式 B)、`nuclear.x86` + flameblox C2 (模式 C) | | `reconcile` | 每个指标:当该指标首次出现时,相关的防御是否已激活?输出:**PRE-DEFENSE** \| **POST-DEFENSE** \| **POST-PARTIAL** \| **UNDEFENDED**,加上与相关防御激活的时间差 | | `bundle` | 原始证据的 Tarball 包:会话 (raw+preauth)、访问日志 (cpanel + apache + cpsrvd)、系统认证日志、cPanel 控制平面状态、每个账户的状态、持久化 (ssh 密钥、cron-all、systemd、sudoers 及其 drop-in、root 历史记录)、防御状态、ps/ss/iptables 快照 | PRE-DEFENSE = 指标出现时主机对该漏洞处于开放状态; POST-DEFENSE = 该指标是附带产物或缓解前的噪音。 **打包结构** (按主机,位于 `/root/.ic5790-forensic/-/`, 权限模式 `0700`): ``` manifest.txt host/uid/cpv/run_id/window/cap sessions.tgz /var/cpanel/sessions/{raw,preauth} (filtered) access-logs.tgz cpsrvd access + incoming_http_requests + error_log + global Apache access/error (NO domlogs) system-logs.tgz /var/log/{secure,messages,audit/audit.log,auth.log}* cpanel-state.tgz accounting.log + resellers + cpanel.config + api_tokens_v2 cpanel-users.tgz /var/cpanel/users/ (split out, per-account state) persistence.tgz ssh keys + all cron tiers + systemd/init.d/profile.d + rc.local + root histories + passwd/group + sudoers + sudoers.d/ (NO /etc/shadow) defense-state.tgz mitigate backups + csf/apf/modsec configs + updatelogs ps.txt `ps auxfww` connections.txt `ss -tnp` (or netstat fallback) iptables.txt `iptables -L -nv` pattern-a-binary-metadata.txt only if /root/sshd present (metadata; binary NOT bundled) user-histories/ per-user .bash_history (gated on --no-history) ``` 在拥有 90 天窗口期的繁忙 cPanel 主机上的典型打包大小:压缩后约 250 MB – 2 GB。每个 tarball 设置 2 GB 上限(`--max-bundle-mb`)会单独剔除过大的候选文件,从而确保打包的其余部分仍能正常生成。
### `sessionscribe-revsnap.sh` - RE 快照收集器 ``` curl -fsSL https://raw.githubusercontent.com/rfxn/cpanel-sessionscribe/main/sessionscribe-revsnap.sh | bash ``` **常见用法:** ``` # 捕获当前 tier(写入至 /var/cpanel/sessionscribe-revsnap/) bash sessionscribe-revsnap.sh # 升级并捕获下一个 tier - RE diff 工作流 /scripts/upcp --force bash sessionscribe-revsnap.sh # 备用输出目录 SNAPDIR=/path/to/snapshots bash sessionscribe-revsnap.sh ```
Tarball 结构 + 行为说明 (点击展开) 每次调用都会生成一个 tarball + sha256,其命名基于 `cpanel -V`、主机名和时间戳。捕获的附带信息旨在支持 BinDiff、Diaphora 和纯文本差异比对工作流并行使用。 ``` cpanel---/ ├── binaries/ cpsrvd, cpsrvd.so, cpanel, whostmgr, … ├── symbols/ │ ├── .strings full strings dump │ ├── .dynsym nm -D │ ├── .objdump-T dynamic symbol table │ ├── .readelf full ELF metadata │ ├── auth-strings/ │ │ ├── *.auth-strings.txt auth|login|session|token|… │ │ └── *.regex-candidates.txt PCRE-shaped strings │ └── disasm/ │ └── *.objdump-d.gz function-level disassembly ├── modules/ │ ├── Cpanel/{Auth,Session,Server,Cookies,…} │ ├── Whostmgr/{Auth,Session,ACLS,…} │ └── _so_files/ cpanel-only .so flattened ├── runtime/ │ ├── preauth-session-schema-sample.txt anonymized baseline │ ├── session-dir-layout.txt │ └── cpsrvd-process-state.txt └── meta/ ├── full-tree-hashes.txt sha256 of every .pm/.so/.pl/exec ├── rpms-cpanel-detailed.txt └── captured-collateral-rationale.txt ``` 我们将其发布在 SessionScribe 之外,是因为它具有通用性:未来每一个 cpsrvd CVE 都将大致落在相同的攻击面上,拥有补丁前后版本的 tarball 对照,将决定分析时间是几小时还是几天。
## 集群化使用 ``` # pdsh + JSONL 汇总(缓解姿态) pdsh -w cpanel-fleet 'bash -s -- --jsonl --quiet' < sessionscribe-mitigate.sh \ | jq -c 'select(.severity != "info")' \ > fleet-mitigate.jsonl # 远程探测扫描 - 遇到任何 VULN 以退出码 2 退出 bash sessionscribe-remote-probe.sh --csv --quiet \ $(awk '{print "--target "$1}' fleet.txt) > fleet-probe.csv echo "any_vuln=$?" # 通过 ssh 进行 IOC 扫描,JSONL 输出至 SIEM for h in $(cat fleet.txt); do ssh "$h" 'bash -s' < sessionscribe-ioc-scan.sh -- --jsonl --quiet done | jq -c '.' > fleet-ioc.jsonl # ansible script module + CSV 合并 ansible -i hosts cpanel -m script -a 'sessionscribe-mitigate.sh --csv --quiet' \ > fleet-mitigate.csv # 跨 fleet 的 kill-chain 协调 - 在广泛扫描时使用 --no-bundle, # 然后仅从高关注主机收集 bundle(v2.0.0+:使用 --full) ansible -i hosts cpanel -m script \ -a 'sessionscribe-ioc-scan.sh --full --no-bundle --jsonl' > fleet-forensic.jsonl jq -r 'select(.phase=="summary" and .key=="verdict" and .note=="COMPROMISED_PRE_DEFENSE") | .host' \ fleet-forensic.jsonl > pre-defense-hosts.txt # 在 pre-defense 子集上进行 bundle 收集 ansible -i pre-defense-hosts.txt all -m script \ -a 'sessionscribe-ioc-scan.sh --full --jsonl --bundle-dir /root/.ic5790-forensic' ``` 探测脚本在集群范围内使用是独立安全的(带有金丝雀标记的会话、主动注销、无更改状态的 API 调用)。宿主机脚本支持 `--quiet` + 结构化输出标志,因此标准输出内容易于被解析器读取。 ## 本工具包不能做什么 明确的非目标: - **不是供应商补丁。** 不会修改 `cpsrvd`、`cpsrvd.so` 或 `Cpanel/Session/*.pm`。cPanel 为您的版本发布的官方移植补丁才是真正的修复;本工具包在此期间关闭了实际的攻击窗口,并在补丁应用后继续作为检测和安全姿态验证发挥作用。 - **不能修复 112、114、116、120、122、128 版本。** 这些版本没有供应商补丁。编排器的 `proxysub` + 防火墙阶段以及 ModSec 规则包可以减小影响范围,但唯一持久的解决方案是升级或迁移。 - **不能替代端口封锁。** ModSec 规则仅对穿过 Apache 的流量生效。`cpsrvd` 直接监听 2082/2083/2086/2087/2095/2096,并且独立于 Apache 可达。请将规则包与 cpsrvd 端口的防火墙策略配合使用。 - **不是漏洞利用代码。** 远程探测不发出任何更改状态的 API 调用,使用 `nxesec_canary_` 属性标记每个测试会话以便取证清理,并在运行结束时主动注销。它模拟该攻击链以产生确定性的判定结果;并没有将其武器化。 - **不是应急响应的替代品。** `sessionscribe-ioc-scan.sh` 用于查找先前被利用的痕迹;它不负责修复它们、跨主机进行狩猎或与账单/客户数据进行关联。请将其 `COMPROMISED` 判定视为启动全面 IR 的触发器,而不是最终结论。 ## 入侵指标 伪造的会话文件格式(被利用后 `/var/cpanel/sessions/raw/` 的内容): ``` local_port=2087 hasroot=1 hulk_registered=1 pass=x origin_as_string=address=127.0.0.1,app=whostmgrd,method=badpass token_denied=1 local_ip_address=127.0.0.1 external_validation_token=cS9C19OfV0hCA4uD cp_security_token=/cpsess6844364556 ip_address=127.0.0.1 user=root tfa_verified=1 successful_internal_auth_with_timestamp=9999999999 port=39040 login_theme=cpanel ``` 正常的预身份验证会话绝不包含 `pass=`、`hasroot=1`、`user=root`、`tfa_verified=1` 或 `successful_internal_auth_with_timestamp=`。其中任何一项与 `origin_as_string=…method=badpass` 结合出现都具有诊断意义。超出 `now+365d` 的伪造时间戳值(例如 `9999999999`)也是独立的诊断依据。 ``` for f in /var/cpanel/sessions/raw/*; do [ -f "$f" ] || continue if grep -q '^token_denied=' "$f" \ && grep -q '^cp_security_token=' "$f" \ && grep -q '^origin_as_string=.*method=badpass' "$f"; then echo "IOC0 hit: $f" fi done ``` 访问日志特征:来自非基线源 IP 且在同一会话窗口内没有前置 `/login/` 200 状态的情况下,对 `/json-api/`、`/execute/` 或 `/scripts2/` 路径成功的 `200`/`302`/`307` 响应。 ## 受影响的版本 ``` 11.86.0.41 (EL6/CL7) 11.110.0.97 11.118.0.63 11.124.0.35 11.126.0.54 11.130.0.19 11.132.0.29 11.134.0.20 11.136.0.5 110.0.103 (EL6/CL6 from .50) WP Squared: 136.1.7 ``` 被排除在供应商补丁列表之外的版本**没有就地修复方案**:112、114、116、120、122、128。运行在这些版本上的主机必须升级到已修补的主系列版本、进行迁移,或者对其 cpsrvd 监听器进行防火墙隔离,直到完成上述操作。 针对 EL6/CL7 的 11.86.0.41 版本在 04/29 的公告修订版中被添加;11.130 在同一修订版中从 `.18` 被提升到 `.19`。随后的修订版添加了 **11.124.0.35**(弥补了之前 124 版的空白)和 **110.0.103**,作为仍停留在 v110.0.50 的 EL6/CL6 主机的直接升级目标,这样它们就不必跨版本直接跃升至 11.86。 ## 优先级顺序 **立即执行** - 为您的版本打补丁(如上所述)。 - 如果您的版本没有补丁,请立即使用防火墙将 TCP 端口 2082、2083、2086、2087、2095、2096 限制为仅允许管理网段 (CIDRs) 访问。计划升级或迁移。 - 在整个集群中运行 `sessionscribe-ioc-scan.sh`。打过补丁的主机仍有可能已经被攻陷。 **后续规划** - 启用代理子域,以便 cPanel/WHM/Webmail 可以通过 Apache 的 80/443 端口访问。 - 将 `modsec-sessionscribe.conf` 部署到 `modsec2.user.conf` 中,并设置 `@ipMatch` 信任列表。 - 使用防火墙将 TCP 端口 2082、2083、2086、2087、2095、2096 限制为仅允许管理网段访问。Apache + ModSecurity 将成为唯一的公共入口。 - 将此代理端点姿态标准化为默认设置。下一次 cpsrvd 公告仍将影响相同的六个端口。 强制执行代理端点的架构层面理由——为什么我们将 SessionScribe 视为停止将 cpsrvd 暴露于开放互联网的转折点,以及如何在不中断客户访问的情况下实现这一点——在这篇[研究文章](https://rfxn.com/research/cpanel-sessionscribe-cve-2026-41940#going-forward)的最后一部分中。 ## 参考 - **研究文章(完整报告):** [rfxn.com/research/cpanel-sessionscribe-cve-202641940](https://rfxn.com/research/cpanel-sessionscribe-cve-2026-41940) - **供应商公告:** [cPanel KB 40073787579671](https://support.cpanel.net/hc/en-us/articles/40073787579671) - **研究员报告:** [watchTowr Labs](https://labs.watchtowr.com/) - **公开 PoC:** [watchtowrlabs/watchTowr-vs-cPanel-WHM-AuthBypass-to-RCE.py](https://github.com/watchtowrlabs/watchTowr-vs-cPanel-WHM-AuthBypass-to-RCE.py) - **源码:** [github.com/rfxn/cpanel-sessionscribe](https://github.com/rfxn/cpanel-sessionscribe) ## 许可证 GPL v2。详情请参见各个文件头。 *在 SessionScribe 事件响应期间编写 - Ryan MacDonald, R-fx Networks.*
标签:0day漏洞, CIDR输入, CISA项目, cPanel, CRLF注入, CVE-2026-41940, IOC扫描, Linux运维, ModSecurity, RCE, SessionScribe, WAF规则, WHM, 云资产清单, 会话伪造, 安全缓解, 安全防护, 应用安全, 未授权访问, 编程工具, 网络安全, 远程代码执行, 远程探测, 逆向工程, 隐私保护