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 披露。

[](https://support.cpanel.net/hc/en-us/articles/40073787579671)
[](#priority-order)
[](https://support.cpanel.net/hc/en-us/articles/40073787579671)
[](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, 云资产清单, 会话伪造, 安全缓解, 安全防护, 应用安全, 未授权访问, 编程工具, 网络安全, 远程代码执行, 远程探测, 逆向工程, 隐私保护